14 Commits

Author SHA1 Message Date
mpabi a535df957d chore: vendor pinned offline simulation sources 2026-07-22 17:27:10 +02:00
user 10a8be72e3 chore: preserve current FreeRTOS and C card updates 2026-07-21 19:14:19 +02:00
user 4785d506fc docs: render FC01 heap4 PDF [skip actions] 2026-07-20 12:08:06 +02:00
user 48eccb5e24 feat: add interactive FC01 heap4 card 2026-07-20 12:04:14 +02:00
user bd34c7ec1e docs: render heap4 PDF for 2b17bb4ff9 [skip actions] 2026-07-14 23:28:28 +02:00
user 2b17bb4ff9 feat: rebuild heap4 card around upstream FreeRTOS allocator 2026-07-14 23:27:13 +02:00
mpabi 032a06fdef docs: checkpoint heap4 card audit 2026-07-14 20:00:00 +02:00
mpabi 8aee0b104c Add rendered PDF [skip actions] 2026-07-14 19:15:59 +02:00
mpabi 49cabf2077 Document FreeRTOS Book v1.1.0 mapping 2026-07-14 19:15:20 +02:00
gitea-actions ef1854cfa0 Render lab-rv32i-freertos-heap4 PDF for a40d2916df [skip actions] 2026-05-07 17:51:29 +00:00
mpabi a40d2916df Document tasks branch namespace 2026-05-07 19:43:33 +02:00
mpabi d589bb8c41 Add rendered PDF [skip actions] 2026-05-02 01:09:48 +02:00
mpabi f4973842ad Clarify heap buffer placement 2026-05-02 01:09:38 +02:00
mpabi 62fb0f8849 Add rendered PDF [skip actions] 2026-05-02 01:06:11 +02:00
186 changed files with 29148 additions and 2076 deletions
+13 -13
View File
@@ -1,18 +1,18 @@
*.aux
*.log
*.out
*.fls
*.fdb_latexmk
*.synctex.gz
*.pdf
build-meta.tex
.rv/
.nvim/
.vim/
.build/
.gdb_history
.stem/
host-build/
build/*/prog.bin
build/*/prog.elf
build/*/prog.lst
build/*/prog.map
build/*/*.o
cmake-build-*/
compile_commands.json
doc/generated/*.aux
doc/generated/*.fdb_latexmk
doc/generated/*.fls
doc/generated/*.log
doc/generated/*.out
doc/generated/*.pdf
doc/generated/*.upa
doc/generated/*.upb
doc/generated/build-meta.tex
+114
View File
@@ -0,0 +1,114 @@
cmake_minimum_required(VERSION 3.13)
if(NOT PICO_SDK_PATH)
if(DEFINED ENV{PICO_SDK_PATH})
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
else()
message(FATAL_ERROR "Set PICO_SDK_PATH to a Raspberry Pi Pico SDK checkout")
endif()
endif()
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(freertos_heap4_rp2350 C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
# The card and allocator are C-only. Debian's compact RISC-V toolchain does
# not ship libstdc++ headers, so keep the SDK's optional new/delete source out
# while preserving the interface targets expected by pico_stdlib.
add_library(pico_cxx_options_headers INTERFACE)
target_include_directories(pico_cxx_options_headers SYSTEM INTERFACE
${PICO_SDK_PATH}/src/rp2_common/pico_cxx_options/include)
add_library(pico_cxx_options INTERFACE)
target_link_libraries(pico_cxx_options INTERFACE pico_cxx_options_headers)
pico_sdk_init()
if(NOT PICO_PLATFORM STREQUAL "rp2350-riscv")
message(FATAL_ERROR "This card targets the RP2350 RISC-V cores")
endif()
if(NOT HEAP4_UPSTREAM_ROOT)
if(DEFINED ENV{HEAP4_UPSTREAM_ROOT})
set(HEAP4_UPSTREAM_ROOT $ENV{HEAP4_UPSTREAM_ROOT})
else()
set(HEAP4_UPSTREAM_ROOT /opt/FreeRTOS-Kernel)
endif()
endif()
if(NOT EXISTS ${HEAP4_UPSTREAM_ROOT}/portable/MemMang/heap_4.c)
message(FATAL_ERROR "Pinned FreeRTOS heap_4.c not found at ${HEAP4_UPSTREAM_ROOT}")
endif()
if(NOT FREERTOS_RP2350_PATH)
if(DEFINED ENV{FREERTOS_RP2350_PATH})
set(FREERTOS_RP2350_PATH $ENV{FREERTOS_RP2350_PATH})
else()
set(FREERTOS_RP2350_PATH /opt/FreeRTOS-RP2350)
endif()
endif()
if(NOT EXISTS ${FREERTOS_RP2350_PATH}/portable/ThirdParty/GCC/RP2350_RISC-V/portASM.S)
message(FATAL_ERROR "Compatible RP2350 FreeRTOS core/port not found at ${FREERTOS_RP2350_PATH}")
endif()
set(FREERTOS_CONFIG_FILE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include)
# Raspberry Pi's port CMake uses FREERTOS_KERNEL_PATH for its matching core.
# Keep the stable allocator root separate and add its heap_4.c explicitly.
set(FREERTOS_KERNEL_PATH ${FREERTOS_RP2350_PATH})
add_subdirectory(
${FREERTOS_RP2350_PATH}/portable/ThirdParty/GCC/RP2350_RISC-V
${CMAKE_BINARY_DIR}/freertos-kernel)
set(HEAP4_PROFILE "observe" CACHE STRING "observe, debug or release")
set_property(CACHE HEAP4_PROFILE PROPERTY STRINGS observe debug release)
set(HEAP4_BINARY_TYPE "no_flash" CACHE STRING "Pico SDK binary type")
set_property(CACHE HEAP4_BINARY_TYPE PROPERTY STRINGS no_flash default)
if(HEAP4_PROFILE STREQUAL "observe")
set(HEAP4_COMPILE_OPTIONS
-O0 -g3 -fno-omit-frame-pointer -fno-inline
-fno-optimize-sibling-calls)
elseif(HEAP4_PROFILE STREQUAL "debug")
set(HEAP4_COMPILE_OPTIONS
-Og -g3 -fno-omit-frame-pointer -fno-optimize-sibling-calls)
elseif(HEAP4_PROFILE STREQUAL "release")
set(HEAP4_COMPILE_OPTIONS -O2)
else()
message(FATAL_ERROR "HEAP4_PROFILE must be observe, debug or release")
endif()
set(MODEL_TASKS
task01_first_fit_split
task02_address_order_coalesce)
foreach(task IN LISTS MODEL_TASKS)
set(task_source src/tasks/${task}.c)
add_executable(${task} ${task_source})
target_include_directories(${task} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/tasks)
set_property(SOURCE ${task_source} APPEND PROPERTY COMPILE_OPTIONS
${HEAP4_COMPILE_OPTIONS} -Wall -Wextra -Wpedantic)
target_link_libraries(${task} pico_stdlib)
pico_enable_stdio_usb(${task} 0)
pico_enable_stdio_uart(${task} 0)
if(NOT HEAP4_BINARY_TYPE STREQUAL "default")
pico_set_binary_type(${task} ${HEAP4_BINARY_TYPE})
endif()
pico_add_extra_outputs(${task})
endforeach()
set(heap4_task task03_freertos_heap4)
set(heap4_source src/tasks/${heap4_task}.c)
add_executable(${heap4_task} ${heap4_source})
target_sources(${heap4_task} PRIVATE
${HEAP4_UPSTREAM_ROOT}/portable/MemMang/heap_4.c)
target_include_directories(${heap4_task} PRIVATE
${CMAKE_CURRENT_LIST_DIR}/include
${CMAKE_CURRENT_LIST_DIR}/src/tasks)
set_property(SOURCE ${heap4_source} APPEND PROPERTY COMPILE_OPTIONS
${HEAP4_COMPILE_OPTIONS} -Wall -Wextra -Wpedantic)
target_link_libraries(${heap4_task} pico_stdlib FreeRTOS-Kernel)
pico_enable_stdio_usb(${heap4_task} 0)
pico_enable_stdio_uart(${heap4_task} 0)
if(NOT HEAP4_BINARY_TYPE STREQUAL "default")
pico_set_binary_type(${heap4_task} ${HEAP4_BINARY_TYPE})
endif()
pico_add_extra_outputs(${heap4_task})
+70 -52
View File
@@ -6,11 +6,18 @@ OBJDUMP := $(RISCV_PREFIX)objdump
SIZE := $(RISCV_PREFIX)size
HOST_CC ?= cc
DEBUG ?= 1
ifeq ($(DEBUG),1)
PROFILE ?= observe
ifeq ($(PROFILE),observe)
OPTFLAGS := -O0 -fno-omit-frame-pointer -fno-inline -fno-optimize-sibling-calls
DBGFLAGS := -g3 -ggdb
else
else ifeq ($(PROFILE),debug)
OPTFLAGS := -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls
DBGFLAGS := -g3 -ggdb
else ifeq ($(PROFILE),release)
OPTFLAGS := -O2
DBGFLAGS :=
else
$(error PROFILE must be observe, debug or release)
endif
RV_ENV_ROOT ?= /opt/rv-env
@@ -21,36 +28,35 @@ LDSCRIPT ?= $(H3_COMMON)/link_hazard3.ld
LAB_RUNTIME_DIR ?= $(RV_ENV_ROOT)/vendor/lab-runtime
MEMOPS ?= $(LAB_RUNTIME_DIR)/memops.c
LOCAL_FREERTOS := $(abspath ../lab-rv32i-freertos-task-stack-vector/vendor/FreeRTOS-Kernel)
FREERTOS_KERNEL_PATH ?= $(if $(wildcard /opt/FreeRTOS-Kernel/include/FreeRTOS.h),/opt/FreeRTOS-Kernel,$(LOCAL_FREERTOS))
FREERTOS_PORT := $(FREERTOS_KERNEL_PATH)/portable/GCC/RISC-V
HEAP4_SOURCE := $(FREERTOS_KERNEL_PATH)/portable/MemMang/heap_4.c
BUILD_ROOT ?= build
HOST_BUILD_ROOT ?= host-build
TASKS := \
task01_heap_map \
task02_free_list \
task03_heap_init \
task04_alignment \
task05_malloc_first_fit \
task06_split_block \
task07_free_insert \
task08_coalescing \
task09_config_macros \
task10_heap4_demo
task01_first_fit_split \
task02_address_order_coalesce \
task03_freertos_heap4
ARCH ?= rv32i_zicsr_zifencei
ABI ?= ilp32
COMMON_FLAGS := -std=c99 -march=$(ARCH) -mabi=$(ABI) -nostdlib -nostartfiles -ffreestanding -fno-builtin -fno-stack-protector $(DBGFLAGS) -Wall -Wextra -Isrc/tasks
LINK_FLAGS := -Wl,--no-relax -Wl,-T,$(LDSCRIPT)
COMMON_FLAGS := -std=c11 -march=$(ARCH) -mabi=$(ABI) -nostdlib -nostartfiles \
-ffreestanding -fno-builtin -fno-stack-protector -ffunction-sections \
-fdata-sections $(OPTFLAGS) $(DBGFLAGS) -Wall -Wextra -Wpedantic
MODEL_CPPFLAGS := -Isrc/tasks
KERNEL_CPPFLAGS := -Iinclude/freestanding -Iinclude -Isrc/tasks \
-I$(FREERTOS_KERNEL_PATH)/include -I$(FREERTOS_PORT)
LINK_FLAGS := -Wl,--no-relax -Wl,--gc-sections -T $(LDSCRIPT)
LIBS := -lgcc
EMIT_FLAGS := -std=c99 -march=$(ARCH) -mabi=$(ABI) -O -ffreestanding -fno-builtin -fno-stack-protector -nostdlib -nostartfiles -Wall -Wextra -Isrc/tasks
CFLAGS_STEP := $(COMMON_FLAGS) -O
HOST_CFLAGS := -std=c99 -O0 -g -Wall -Wextra -DHOST_PRINTF=1 -Isrc/tasks
HOST_FLAGS := -std=c11 $(OPTFLAGS) $(DBGFLAGS) -Wall -Wextra -Wpedantic \
-DHOST_PRINTF=1 -Isrc/tasks
HOST_HEAP4_FLAGS := $(HOST_FLAGS) -DHEAP4_HOST_ADAPTER=1 -Iinclude/host-shim
.PHONY: all tasks host \
task1 task2 task3 task4 task5 task6 task7 task8 task9 task10 \
t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 \
host-task1 host-task2 host-task3 host-task4 host-task5 host-task6 host-task7 host-task8 host-task9 host-task10 \
ht1 ht2 ht3 ht4 ht5 ht6 ht7 ht8 ht9 ht10 \
clean check-support $(TASKS)
.PHONY: all tasks host task1 task2 task3 t1 t2 t3 \
host-task1 host-task2 host-task3 ht1 ht2 ht3 clean check-support check-kernel \
$(TASKS)
all: tasks
tasks: $(TASKS)
@@ -59,54 +65,66 @@ host: $(addprefix $(HOST_BUILD_ROOT)/,$(addsuffix /prog,$(TASKS)))
task1 t1: $(word 1,$(TASKS))
task2 t2: $(word 2,$(TASKS))
task3 t3: $(word 3,$(TASKS))
task4 t4: $(word 4,$(TASKS))
task5 t5: $(word 5,$(TASKS))
task6 t6: $(word 6,$(TASKS))
task7 t7: $(word 7,$(TASKS))
task8 t8: $(word 8,$(TASKS))
task9 t9: $(word 9,$(TASKS))
task10 t10: $(word 10,$(TASKS))
host-task1 ht1: $(HOST_BUILD_ROOT)/$(word 1,$(TASKS))/prog
host-task2 ht2: $(HOST_BUILD_ROOT)/$(word 2,$(TASKS))/prog
host-task3 ht3: $(HOST_BUILD_ROOT)/$(word 3,$(TASKS))/prog
host-task4 ht4: $(HOST_BUILD_ROOT)/$(word 4,$(TASKS))/prog
host-task5 ht5: $(HOST_BUILD_ROOT)/$(word 5,$(TASKS))/prog
host-task6 ht6: $(HOST_BUILD_ROOT)/$(word 6,$(TASKS))/prog
host-task7 ht7: $(HOST_BUILD_ROOT)/$(word 7,$(TASKS))/prog
host-task8 ht8: $(HOST_BUILD_ROOT)/$(word 8,$(TASKS))/prog
host-task9 ht9: $(HOST_BUILD_ROOT)/$(word 9,$(TASKS))/prog
host-task10 ht10: $(HOST_BUILD_ROOT)/$(word 10,$(TASKS))/prog
check-support:
@test -f "$(H3_INIT)" || { echo "Missing $(H3_INIT). Run inside rv-env or set RV_ENV_ROOT=/path/to/rv32i-hazard3-env" >&2; exit 1; }
@test -f "$(H3_INIT)" || { echo "Missing $(H3_INIT). Run inside hazard3-sim." >&2; exit 1; }
@test -f "$(LDSCRIPT)" || { echo "Missing $(LDSCRIPT)." >&2; exit 1; }
@test -f "$(MEMOPS)" || { echo "Missing $(MEMOPS)." >&2; exit 1; }
define task_rules
$(1): $(BUILD_ROOT)/$(1)/prog.bin $(BUILD_ROOT)/$(1)/prog.elf $(BUILD_ROOT)/$(1)/prog.lst $(BUILD_ROOT)/$(1)/$(1).s
check-kernel:
@test -f "$(HEAP4_SOURCE)" || { echo "Missing pinned FreeRTOS heap_4.c at $(HEAP4_SOURCE)." >&2; exit 1; }
@grep -F 'FreeRTOS Kernel V11.3.0' "$(FREERTOS_KERNEL_PATH)/include/FreeRTOS.h" >/dev/null || { echo "Expected FreeRTOS Kernel V11.3.0." >&2; exit 1; }
$(BUILD_ROOT)/$(1)/prog.elf: crt0.S src/tasks/$(1).c src/tasks/heap4_common.h $(H3_INIT) $(LDSCRIPT) $(MEMOPS) | check-support
define model_task_rules
$(1): $(BUILD_ROOT)/$(1)/prog.bin $(BUILD_ROOT)/$(1)/prog.elf $(BUILD_ROOT)/$(1)/prog.lst
$(BUILD_ROOT)/$(1)/prog.elf: crt0.S src/tasks/$(1).c src/tasks/heap4_model.h $(H3_INIT) $(LDSCRIPT) $(MEMOPS) | check-support
mkdir -p $$(@D)
$(CC) $(CFLAGS_STEP) $(LINK_FLAGS) -Wl,-Map,$(BUILD_ROOT)/$(1)/prog.map -o $$@ $(H3_INIT) crt0.S $(MEMOPS) src/tasks/$(1).c $(LIBS)
$(CC) $(COMMON_FLAGS) $(MODEL_CPPFLAGS) $(LINK_FLAGS) -Wl,-Map,$(BUILD_ROOT)/$(1)/prog.map -o $$@ $(H3_INIT) crt0.S $(MEMOPS) src/tasks/$(1).c $(LIBS)
$(SIZE) -A -x $$@
$(BUILD_ROOT)/$(1)/prog.bin: $(BUILD_ROOT)/$(1)/prog.elf
$(OBJCOPY) -O binary $$< $$@
$(BUILD_ROOT)/$(1)/prog.lst: $(BUILD_ROOT)/$(1)/prog.elf
$(OBJDUMP) -d -M no-aliases,numeric $$< > $$@
$(OBJDUMP) -d -S -M no-aliases,numeric $$< > $$@
$(BUILD_ROOT)/$(1)/$(1).s: src/tasks/$(1).c src/tasks/heap4_common.h | check-support
$(HOST_BUILD_ROOT)/$(1)/prog: src/tasks/$(1).c src/tasks/heap4_model.h
mkdir -p $$(@D)
$(CC) $(EMIT_FLAGS) -S -o $$@ $$<
$(HOST_BUILD_ROOT)/$(1)/prog: src/tasks/$(1).c src/tasks/heap4_common.h
mkdir -p $$(@D)
$(HOST_CC) $(HOST_CFLAGS) -o $$@ $$<
$(HOST_CC) $(HOST_FLAGS) -o $$@ $$<
endef
$(foreach task,$(TASKS),$(eval $(call task_rules,$(task))))
$(eval $(call model_task_rules,$(word 1,$(TASKS))))
$(eval $(call model_task_rules,$(word 2,$(TASKS))))
TASK03 := $(word 3,$(TASKS))
TASK03_KERNEL_SOURCES := \
$(FREERTOS_KERNEL_PATH)/tasks.c \
$(FREERTOS_KERNEL_PATH)/list.c \
$(HEAP4_SOURCE) \
$(FREERTOS_PORT)/port.c \
$(FREERTOS_PORT)/portASM.S
$(TASK03): $(BUILD_ROOT)/$(TASK03)/prog.bin $(BUILD_ROOT)/$(TASK03)/prog.elf $(BUILD_ROOT)/$(TASK03)/prog.lst
$(BUILD_ROOT)/$(TASK03)/prog.elf: crt0.S src/tasks/$(TASK03).c include/FreeRTOSConfig.h $(TASK03_KERNEL_SOURCES) $(H3_INIT) $(LDSCRIPT) $(MEMOPS) | check-support check-kernel
mkdir -p $(@D)
$(CC) $(COMMON_FLAGS) $(KERNEL_CPPFLAGS) $(LINK_FLAGS) -Wl,-Map,$(BUILD_ROOT)/$(TASK03)/prog.map -o $@ $(H3_INIT) crt0.S $(MEMOPS) $(TASK03_KERNEL_SOURCES) src/tasks/$(TASK03).c $(LIBS)
$(SIZE) -A -x $@
$(BUILD_ROOT)/$(TASK03)/prog.bin: $(BUILD_ROOT)/$(TASK03)/prog.elf
$(OBJCOPY) -O binary $< $@
$(BUILD_ROOT)/$(TASK03)/prog.lst: $(BUILD_ROOT)/$(TASK03)/prog.elf
$(OBJDUMP) -d -S -M no-aliases,numeric $< > $@
$(HOST_BUILD_ROOT)/$(TASK03)/prog: src/tasks/$(TASK03).c include/host-shim/FreeRTOS.h include/host-shim/task.h $(HEAP4_SOURCE) | check-kernel
mkdir -p $(@D)
$(HOST_CC) $(HOST_HEAP4_FLAGS) -o $@ src/tasks/$(TASK03).c $(HEAP4_SOURCE)
clean:
rm -rf $(BUILD_ROOT) $(HOST_BUILD_ROOT)
+115 -30
View File
@@ -1,45 +1,130 @@
# rv32i-freertos / heap4
# Karta pracy: FreeRTOS `heap_4`
Karta pracy 1/? z serii `rv32i-freertos`.
Trzy programy wspierające kartę po K&R 5.4 oraz `structures`:
Temat: mechanika alokatora `heap_4.c` z FreeRTOS-a na małych przykładach
uruchamianych w środowisku RV32I.
1. nagłówek, wyrównanie i geometria splitu na arenie 256 B;
2. lista adresowa + scalanie `A | B | C`;
3. niezmodyfikowany FreeRTOS-Kernel V11.3.0 `heap_4.c`.
Ta karta jest pomostem po kartach C: używa wskaźników, struktur, listy
jednokierunkowej, `static`, `typedef`, makr i arytmetyki adresów, ale nie
wymaga jeszcze schedulera, tasków ani przerwań.
W lekcji 30-minutowej programy 12 są demonstracjami z predykcją. Uczeń
debugguje wyłącznie program 3.
Kod w `src/tasks` jest dydaktycznym modelem zachowania `heap_4`, a nie kopią
pliku upstream FreeRTOS.
Task 3 linkuje upstream `portable/MemMang/heap_4.c` z commita
`9b777ae5c5b8e9e456065a00294d1e5f5f9facf5`.
## Taski
- AMD64: rzeczywisty algorytm + jednowątkowy adapter sekcji krytycznej.
- Hazard3: `tasks.c`, `list.c`, `heap_4.c` i oficjalny port RISC-V.
- RP2350: zgodny core+port z forka Raspberry Pi (`4f7299d6ea74`) oraz
podstawiony stabilny allocator V11.3.0.
- `task01_heap_map.c` - mapa pojęć: sterta, blok, nagłówek bloku.
- `task02_free_list.c` - lista wolnych bloków.
- `task03_heap_init.c` - inicjalizacja sterty i wartownik końca listy.
- `task04_alignment.c` - wyrównanie rozmiarów i adresów.
- `task05_malloc_first_fit.c` - pierwszy pasujący wolny blok.
- `task06_split_block.c` - podział większego bloku.
- `task07_free_insert.c` - zwracanie bloku na listę wolnych.
- `task08_coalescing.c` - scalanie sąsiednich wolnych bloków.
- `task09_config_macros.c` - makra konfiguracyjne i warunkowy `printf`.
- `task10_heap4_demo.c` - mini-demonstracja `malloc/free` bez pełnego RTOS-a.
## Bezpośrednia kontynuacja K&R 5.4
Hostowe `printf` jest dostępne tylko przez `-DHOST_PRINTF=1`; RV32I zapisuje
wyniki w globalnych zmiennych `volatile`.
Lekcja zaczyna się od gotowego projektu z poprzedniej karty:
`allocbuf[64]`, `allocp`, `alloc(5)`, `alloc(7)` oraz offsety
`0 -> 5 -> 12`. Ten alokator:
## Budowanie
- nie potrafi zwolnić pierwszego bloku i zachować drugiego;
- nie przechowuje rozmiaru ani stanu zwróconego obszaru;
- nie zapamiętuje i nie wykorzystuje dziur;
- odzyskuje pamięć wyłącznie przez reset całej areny.
FC01 odpowiada na jedno pytanie projektowe: **co trzeba dodać, aby zwalniać
dowolny blok i ponownie wykorzystywać powstałe miejsce?** Odpowiedzią jest
mechanika `heap_4`: nagłówek bloku, lista wolnych bloków uporządkowana po
adresie, first-fit, split oraz scalanie fizycznych sąsiadów.
FreeRTOS udostępnia kilka wymiennych implementacji tego samego API i projekt
linkuje dokładnie jedną. `heap_1`, `heap_2`, `heap_3` i `heap_5` są w tej
karcie tylko jednominutową mapą kontekstu. Cała obserwowana implementacja to
niezmodyfikowany [`heap_4.c` z FreeRTOS-Kernel V11.3.0](https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/V11.3.0/portable/MemMang/heap_4.c).
Lekcja trwa 30 minut. Task 1 i Task 2 są krótkimi demonstracjami z predykcją;
Task 3 jest jedynym centralnym przebiegiem ucznia w debuggerze. Obraz dla
Pico 2 W musi być zbudowany i wgrany przed zajęciami. `heap_4` jest wyborem
linkera projektu, nie sprzętowym mechanizmem RP2350.
## Jedno wejście: `stemctl`
Centralny przebieg lekcji, przygotowany przed wejściem uczniów:
```bash
RV_ENV_ROOT=~/dev/workspace/rv/tools/rv32i-hazard3-student-env make tasks
make host
stemctl debug rp2350 freertos heap4 3 --device /dev/bus/usb/BBB/DDD
```
## Praca przez rvctl
Pełna weryfikacja repozytorium wykonywana poza 30-minutową lekcją:
```bash
./rvctl series cards fetch inf heap4
./rvctl card use inf heap4
./rvctl tasks list
./rvctl debug rv32i 1
stemctl series cards fetch freertos heap4
stemctl test native-amd64 freertos heap4 1
stemctl test hazard3-sim freertos heap4 2
stemctl debug hazard3-sim freertos heap4 3
stemctl test rp2350 freertos heap4 3
stemctl deploy rp2350 freertos heap4 3 --device /dev/bus/usb/BBB/DDD
stemctl debug rp2350 freertos heap4 3 --device /dev/bus/usb/BBB/DDD
```
- `debug rp2350`: roboczy ELF `no_flash` ładowany do SRAM.
- `deploy rp2350`: trwały obraz w SPI flash, odczytany przez breakpointy.
## Kontrakt zadań
| Task | Sprawdza | Wynik kluczowy |
|---|---|---|
| 1 | alignment, header, split | `wanted + remainder == 256` |
| 2 | lista po adresie, coalescing | `2 bloki -> 1 blok`, `96 -> 144 B` |
| 3 | prawdziwe API FreeRTOS | free wraca do F0, minimum nie rośnie, OOM = NULL |
## Debugowanie Task 3
Automatyczny start: `heap4_reset_checkpoint`. Karta prowadzi po ośmiu
powtarzalnych stanach jednego procesu:
| Event | Symbol | Najważniejszy dowód Hazard3 |
|---|---|---|
| E01 | `heap4_reset_checkpoint` | `free=largest=4080`, 1 blok |
| E02 | `heap4_alloc_a_checkpoint` | A(24), `consumed=48`, `free=4032` |
| E03 | `heap4_alloc_b_checkpoint` | B(40), `consumed=64`, `free=3968` |
| E04 | `heap4_alloc_c_checkpoint` | C(16), `consumed=32`, `free=3936` |
| E05 | `heap4_free_a_checkpoint` | `free=3984`, 2 bloki |
| E06 | `heap4_fragmented_checkpoint` | `free=4016`, 2 bloki, `free>largest` |
| E07 | `heap4_coalesced_checkpoint` | `free=largest=4080`, 1 blok |
| E08 | `task03_debug_checkpoint` | OOM `NULL`, hook=1, asserts=0, PASS=1 |
`BlockLink_t` ma na RV32 8 B, ale port wymaga wyrównania 16 B, dlatego
`xHeapStructSize` wynosi 16 B. To rozróżnienie tłumaczy zużycie 48/64/32 B.
```gdb
p g_fragmented_stats
p g_final_stats
p g_a_consumed
p g_b_consumed
p g_c_consumed
p xFreeBytesRemaining
p xMinimumEverFreeBytesRemaining
p xStart
p pxEnd
x/160bx ucHeap
b prvInsertBlockIntoFreeList
b pvPortMalloc
b vPortFree
```
Scalanie sprawdzamy z replay E06: zatrzymujemy się przed `vPortFree(g_b)`,
ustawiamy tymczasowy breakpoint na `prvInsertBlockIntoFreeList` i krokujemy.
Upstream V11.3.0 scala najpierw poprzedni/lewy, a potem następny/prawy blok.
Pełna macierz kroków diagramów jest w
[`doc/hazard3-iteration-plan.md`](doc/hazard3-iteration-plan.md).
W UI: Termdebug po lewej; źródło i listing po prawej; bash pod Neovimem.
`F5` continue, `F9` breakpoint, `F10` next, `F11` step, `Shift-F11`
finish, `F8` stepi, `:StudentStack`, `:StudentMemory`, `:StudentLst`.
## Test repozytorium
```bash
./tests/test_host.sh
bash -n tools/card-action.sh
./scripts/render_pdf.sh
```
Źródło i licencja: [UPSTREAM.md](UPSTREAM.md).
+19
View File
@@ -0,0 +1,19 @@
# Upstream contract
- Project: FreeRTOS-Kernel
- Version: V11.3.0
- Commit: `9b777ae5c5b8e9e456065a00294d1e5f5f9facf5`
- Source used by Task 3: `portable/MemMang/heap_4.c`
- License: MIT (as shipped in the pinned upstream checkout)
RP2350 needs a port that is newer than the V11.3.0 tag:
- Project: Raspberry Pi fork of FreeRTOS-Kernel
- Commit: `4f7299d6ea746b27a9dd19e87af568e34bd65b15`
- Files used on RP2350: matching kernel core plus
`portable/ThirdParty/GCC/RP2350_RISC-V`
- Allocator used on RP2350: still the stable V11.3.0 `heap_4.c` above,
compiled instead of the fork's development-branch allocator.
The canonical containers provide `/opt/FreeRTOS-Kernel`; the RP2350 profile
also provides `/opt/FreeRTOS-RP2350`. The card carries no allocator copy.
-53
View File
@@ -1,53 +0,0 @@
.file "task01_heap_map.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
li a5,8
sw a5,g_header_size,a4
sw a5,g_aligned_header_size,a4
li a5,1
sw a5,g_allocated_bit_set,a4
lla a5,g_total_block_bytes
li a4,24
sw a4,0(a5)
lw a5,0(a5)
addi a5,a5,-8
sw a5,g_user_bytes,a4
li a0,0
ret
.size main, .-main
.globl g_total_block_bytes
.globl g_user_bytes
.globl g_allocated_bit_set
.globl g_aligned_header_size
.globl g_header_size
.section .sbss,"aw",@nobits
.align 2
.type g_total_block_bytes, @object
.size g_total_block_bytes, 4
g_total_block_bytes:
.zero 4
.type g_user_bytes, @object
.size g_user_bytes, 4
g_user_bytes:
.zero 4
.type g_allocated_bit_set, @object
.size g_allocated_bit_set, 4
g_allocated_bit_set:
.zero 4
.type g_aligned_header_size, @object
.size g_aligned_header_size, 4
g_aligned_header_size:
.zero 4
.type g_header_size, @object
.size g_header_size, 4
g_header_size:
.zero 4
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-73
View File
@@ -1,73 +0,0 @@
.file "task02_free_list.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
addi sp,sp,-32
addi a5,sp,16
sw a5,24(sp)
li a5,32
sw a5,28(sp)
addi a5,sp,8
sw a5,16(sp)
li a5,80
sw a5,20(sp)
sw zero,8(sp)
li a5,24
sw a5,12(sp)
li a4,0
add a5,sp,a5
.L2:
lw a3,4(a5)
add a4,a4,a3
lw a5,0(a5)
bne a5,zero,.L2
sw a4,g_free_total,a5
li a3,0
addi a5,sp,24
j .L4
.L3:
lw a5,0(a5)
beq a5,zero,.L10
.L4:
lw a4,4(a5)
bgeu a3,a4,.L3
mv a3,a4
j .L3
.L10:
sw a3,g_largest_free,a5
addi a5,sp,24
li a4,0
.L5:
addi a4,a4,1
lw a5,0(a5)
bne a5,zero,.L5
sw a4,g_node_count,a5
li a0,0
addi sp,sp,32
jr ra
.size main, .-main
.globl g_node_count
.globl g_largest_free
.globl g_free_total
.section .sbss,"aw",@nobits
.align 2
.type g_node_count, @object
.size g_node_count, 4
g_node_count:
.zero 4
.type g_largest_free, @object
.size g_largest_free, 4
g_largest_free:
.zero 4
.type g_free_total, @object
.size g_free_total, 4
g_free_total:
.zero 4
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-74
View File
@@ -1,74 +0,0 @@
.file "task03_heap_init.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a2,.LANCHOR0
andi a4,a2,7
mv a5,a2
beq a4,zero,.L2
andi a5,a2,-8
addi a5,a5,8
.L2:
lla a3,.LANCHOR0+160
sub a3,a3,a5
addi a4,a5,-8
andi a3,a3,-8
add a4,a4,a3
sw a4,end_marker,a3
sw zero,0(a4)
sw zero,4(a4)
sw a4,0(a5)
sub a3,a4,a5
sw a3,4(a5)
lla a1,start
sw a5,0(a1)
sw zero,4(a1)
sub a5,a5,a2
sw a5,g_aligned_offset,a2
sw a3,g_initial_free_size,a5
lw a5,0(a4)
seqz a5,a5
sw a5,g_end_is_last,a4
li a0,0
ret
.size main, .-main
.globl g_end_is_last
.globl g_aligned_offset
.globl g_initial_free_size
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 168
heap_area:
.zero 168
.section .sbss,"aw",@nobits
.align 2
.type g_end_is_last, @object
.size g_end_is_last, 4
g_end_is_last:
.zero 4
.type g_aligned_offset, @object
.size g_aligned_offset, 4
g_aligned_offset:
.zero 4
.type g_initial_free_size, @object
.size g_initial_free_size, 4
g_initial_free_size:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-47
View File
@@ -1,47 +0,0 @@
.file "task04_alignment.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
addi sp,sp,-32
li a5,16
sw a5,g_request_1,a4
li a5,24
sw a5,g_request_9,a4
li a5,32
sw a5,g_request_17,a4
li a5,7
sw a5,g_address_delta,a4
li a0,0
addi sp,sp,32
jr ra
.size main, .-main
.globl g_address_delta
.globl g_request_17
.globl g_request_9
.globl g_request_1
.section .sbss,"aw",@nobits
.align 2
.type g_address_delta, @object
.size g_address_delta, 4
g_address_delta:
.zero 4
.type g_request_17, @object
.size g_request_17, 4
g_request_17:
.zero 4
.type g_request_9, @object
.size g_request_9, 4
g_request_9:
.zero 4
.type g_request_1, @object
.size g_request_1, 4
g_request_1:
.zero 4
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
@@ -1,95 +0,0 @@
.file "task05_malloc_first_fit.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a4,.LANCHOR0
andi a5,a4,7
beq a5,zero,.L2
andi a4,a4,-8
addi a4,a4,8
.L2:
mv a5,a4
addi a3,a4,120
sw a3,end_marker,a2
sw zero,120(a4)
sw zero,4(a3)
sw a3,0(a4)
li a2,120
sw a2,4(a4)
lla a2,start
sw a4,0(a2)
sw zero,4(a2)
li a1,31
j .L5
.L6:
mv a5,a4
.L5:
lw a4,4(a5)
bgtu a4,a1,.L7
lw a4,0(a5)
mv a2,a5
bne a4,a3,.L6
li a5,0
j .L4
.L7:
lw a4,0(a5)
sw a4,0(a2)
sw zero,0(a5)
lw a4,4(a5)
li a3,-2147483648
or a4,a4,a3
sw a4,4(a5)
addi a5,a5,8
.L4:
snez a4,a5
sw a4,g_allocation_ok,a3
lw a5,-4(a5)
slli a5,a5,1
srli a5,a5,1
sw a5,g_allocated_size,a4
lw a5,start
lw a5,4(a5)
sw a5,g_remaining_free,a4
li a0,0
ret
.size main, .-main
.globl g_allocation_ok
.globl g_remaining_free
.globl g_allocated_size
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 136
heap_area:
.zero 136
.section .sbss,"aw",@nobits
.align 2
.type g_allocation_ok, @object
.size g_allocation_ok, 4
g_allocation_ok:
.zero 4
.type g_remaining_free, @object
.size g_remaining_free, 4
g_remaining_free:
.zero 4
.type g_allocated_size, @object
.size g_allocated_size, 4
g_allocated_size:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
@@ -1,117 +0,0 @@
.file "task06_split_block.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a4,.LANCHOR0
andi a5,a4,7
beq a5,zero,.L2
andi a4,a4,-8
addi a4,a4,8
.L2:
mv a5,a4
addi a3,a4,176
sw a3,end_marker,a2
sw zero,176(a4)
sw zero,4(a3)
sw a3,0(a4)
li a2,176
sw a2,4(a4)
lla a2,start
sw a4,0(a2)
sw zero,4(a2)
li a1,39
j .L7
.L10:
mv a5,a4
.L7:
lw a4,4(a5)
bgtu a4,a1,.L13
lw a4,0(a5)
mv a2,a5
bne a4,a3,.L10
li a5,0
j .L6
.L13:
addi a4,a4,-40
li a1,16
bleu a4,a1,.L4
sw a4,44(a5)
lw a4,0(a5)
sw a4,40(a5)
addi a4,a5,40
sw a4,0(a2)
li a4,40
sw a4,4(a5)
.L5:
sw zero,0(a5)
lw a4,4(a5)
li a2,-2147483648
or a4,a4,a2
sw a4,4(a5)
addi a5,a5,8
.L6:
lw a5,-4(a5)
slli a5,a5,1
srli a5,a5,1
sw a5,g_allocated_size,a4
lw a5,start
lw a4,4(a5)
sw a4,g_split_free_size,a2
beq a5,a3,.L11
li a4,0
.L9:
addi a4,a4,1
lw a5,0(a5)
bne a5,a3,.L9
.L8:
sw a4,g_free_nodes,a5
li a0,0
ret
.L4:
lw a4,0(a5)
sw a4,0(a2)
j .L5
.L11:
li a4,0
j .L8
.size main, .-main
.globl g_free_nodes
.globl g_split_free_size
.globl g_allocated_size
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 200
heap_area:
.zero 200
.section .sbss,"aw",@nobits
.align 2
.type g_free_nodes, @object
.size g_free_nodes, 4
g_free_nodes:
.zero 4
.type g_split_free_size, @object
.size g_split_free_size, 4
g_split_free_size:
.zero 4
.type g_allocated_size, @object
.size g_allocated_size, 4
g_allocated_size:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
@@ -1,116 +0,0 @@
.file "task07_free_insert.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a5,.LANCHOR0
lla a4,.LANCHOR0+176
sw a4,end_marker,a3
sw a5,start,a3
lla a3,.LANCHOR0+128
sw a3,0(a5)
sw a4,128(a5)
sw zero,176(a5)
li a4,40
sw a4,4(a5)
li a4,48
sw a4,132(a5)
sw zero,180(a5)
li a4,32
sw a4,68(a5)
lla a5,start
lla a3,.LANCHOR0+64
.L2:
mv a4,a5
lw a5,0(a5)
bltu a5,a3,.L2
lla a3,.LANCHOR0
sw a5,64(a3)
lla a5,.LANCHOR0+64
sw a5,0(a4)
lw a5,start
beq a5,a3,.L16
sw zero,g_order_ok,a4
lla a4,.LANCHOR0+176
beq a5,a4,.L5
.L9:
mv a4,a5
li a3,0
lla a2,.LANCHOR0+176
.L6:
addi a3,a3,1
lw a4,0(a4)
bne a4,a2,.L6
sw a3,g_free_count,a4
li a4,0
lla a2,.LANCHOR0+176
.L7:
lw a3,4(a5)
add a4,a4,a3
lw a5,0(a5)
bne a5,a2,.L7
.L8:
sw a4,g_total_free,a5
li a0,0
ret
.L16:
lla a3,.LANCHOR0+64
lw a2,.LANCHOR0
li a4,0
beq a2,a3,.L17
.L4:
sw a4,g_order_ok,a3
j .L9
.L17:
lla a3,.LANCHOR0+128
lw a2,.LANCHOR0+64
bne a2,a3,.L4
lla a3,.LANCHOR0+176
lw a4,.LANCHOR0+128
sub a4,a4,a3
seqz a4,a4
j .L4
.L5:
sw zero,g_free_count,a5
li a4,0
j .L8
.size main, .-main
.globl g_total_free
.globl g_free_count
.globl g_order_ok
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 192
heap_area:
.zero 192
.section .sbss,"aw",@nobits
.align 2
.type g_total_free, @object
.size g_total_free, 4
g_total_free:
.zero 4
.type g_free_count, @object
.size g_free_count, 4
g_free_count:
.zero 4
.type g_order_ok, @object
.size g_order_ok, 4
g_order_ok:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-116
View File
@@ -1,116 +0,0 @@
.file "task08_coalescing.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a5,.LANCHOR0
lla a4,.LANCHOR0+160
sw a4,end_marker,a3
sw a5,start,a3
lla a3,.LANCHOR0+96
sw a3,0(a5)
sw a4,96(a5)
sw zero,160(a5)
li a4,48
sw a4,4(a5)
sw a4,52(a5)
li a4,64
sw a4,100(a5)
sw zero,164(a5)
lla a5,start
lla a3,.LANCHOR0+48
.L2:
mv a4,a5
lw a5,0(a5)
bltu a5,a3,.L2
lla a3,.LANCHOR0+96
beq a5,a3,.L12
.L3:
sw a5,.LANCHOR0+48,a3
lla a5,start
lla a3,.LANCHOR0+48
beq a4,a5,.L4
lw a2,4(a4)
slli a5,a2,1
srli a5,a5,1
add a5,a4,a5
beq a5,a3,.L13
.L4:
sw a3,0(a4)
lw a2,start
lla a5,.LANCHOR0+160
beq a2,a5,.L9
mv a5,a2
li a4,0
lla a3,.LANCHOR0+160
.L6:
addi a4,a4,1
lw a5,0(a5)
bne a5,a3,.L6
.L5:
sw a4,g_free_count,a5
lw a5,4(a2)
sw a5,g_merged_size,a4
lw a5,0(a2)
lla a4,.LANCHOR0+160
sub a5,a5,a4
seqz a5,a5
sw a5,g_merged_with_right,a4
li a0,0
ret
.L12:
li a5,112
sw a5,.LANCHOR0+52,a3
lw a5,0(a4)
lw a5,0(a5)
j .L3
.L13:
lla a3,.LANCHOR0
lw a5,52(a3)
add a5,a5,a2
sw a5,4(a4)
lw a3,48(a3)
j .L4
.L9:
li a4,0
j .L5
.size main, .-main
.globl g_merged_with_right
.globl g_merged_size
.globl g_free_count
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 192
heap_area:
.zero 192
.section .sbss,"aw",@nobits
.align 2
.type g_merged_with_right, @object
.size g_merged_with_right, 4
g_merged_with_right:
.zero 4
.type g_merged_size, @object
.size g_merged_size, 4
g_merged_size:
.zero 4
.type g_free_count, @object
.size g_free_count, 4
g_free_count:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
@@ -1,44 +0,0 @@
.file "task09_config_macros.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
li a5,256
sw a5,g_total_heap,a4
li a5,248
sw a5,g_adjusted_heap,a4
li a5,1
sw a5,g_dynamic_enabled,a4
sw zero,g_trace_is_host_only,a5
li a0,0
ret
.size main, .-main
.globl g_trace_is_host_only
.globl g_dynamic_enabled
.globl g_adjusted_heap
.globl g_total_heap
.section .sbss,"aw",@nobits
.align 2
.type g_trace_is_host_only, @object
.size g_trace_is_host_only, 4
g_trace_is_host_only:
.zero 4
.type g_dynamic_enabled, @object
.size g_dynamic_enabled, 4
g_dynamic_enabled:
.zero 4
.type g_adjusted_heap, @object
.size g_adjusted_heap, 4
g_adjusted_heap:
.zero 4
.type g_total_heap, @object
.size g_total_heap, 4
g_total_heap:
.zero 4
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-253
View File
@@ -1,253 +0,0 @@
.file "task10_heap4_demo.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.type heap_malloc_model, @function
heap_malloc_model:
beq a0,zero,.L1
addi a4,a0,8
andi a0,a0,7
beq a0,zero,.L3
andi a4,a4,-8
addi a4,a4,8
.L3:
lw a0,start
lw a3,end_marker
beq a0,a3,.L10
lla a2,start
j .L8
.L11:
mv a0,a5
.L8:
lw a5,4(a0)
bgeu a5,a4,.L12
lw a5,0(a0)
mv a2,a0
bne a5,a3,.L11
li a0,0
ret
.L12:
sub a3,a5,a4
li a1,16
bleu a3,a1,.L5
add a5,a0,a4
sw a3,4(a5)
lw a3,0(a0)
sw a3,0(a5)
sw a5,0(a2)
sw a4,4(a0)
.L6:
sw zero,0(a0)
lw a5,4(a0)
li a3,-2147483648
or a5,a5,a3
sw a5,4(a0)
lla a3,free_bytes_remaining
lw a5,0(a3)
sub a5,a5,a4
sw a5,0(a3)
lw a4,minimum_ever_free
bgeu a5,a4,.L7
sw a5,minimum_ever_free,a4
.L7:
addi a0,a0,8
ret
.L5:
lw a4,0(a0)
sw a4,0(a2)
mv a4,a5
j .L6
.L10:
li a0,0
.L1:
ret
.size heap_malloc_model, .-heap_malloc_model
.align 2
.type heap_free_model, @function
heap_free_model:
beq a0,zero,.L13
lw a5,-4(a0)
blt a5,zero,.L19
.L13:
ret
.L19:
addi a3,a0,-8
slli a5,a5,1
srli a2,a5,1
sw a2,-4(a0)
lla a4,free_bytes_remaining
lw a5,0(a4)
add a5,a5,a2
sw a5,0(a4)
lla a5,start
.L15:
mv a4,a5
lw a5,0(a5)
bgtu a3,a5,.L15
add a1,a3,a2
beq a5,a1,.L20
.L16:
sw a5,-8(a0)
lla a5,start
beq a4,a5,.L17
lw a2,4(a4)
slli a5,a2,1
srli a5,a5,1
add a5,a4,a5
beq a3,a5,.L21
.L17:
sw a3,0(a4)
ret
.L20:
lw a5,4(a5)
add a5,a5,a2
sw a5,-4(a0)
lw a5,0(a4)
lw a5,0(a5)
j .L16
.L21:
lw a5,-4(a0)
add a5,a5,a2
sw a5,4(a4)
lw a5,-8(a0)
sw a5,0(a4)
ret
.size heap_free_model, .-heap_free_model
.align 2
.globl main
.type main, @function
main:
addi sp,sp,-32
sw ra,28(sp)
sw s0,24(sp)
sw s1,20(sp)
sw s2,16(sp)
sw s3,12(sp)
sw s4,8(sp)
lla a4,.LANCHOR0
andi a3,a4,7
mv a5,a4
beq a3,zero,.L23
andi a4,a4,-8
addi a5,a4,8
.L23:
lla s0,.LANCHOR0+256
sub s0,s0,a5
andi s0,s0,-8
addi a4,a5,-8
add s0,s0,a4
sw s0,end_marker,a4
sw zero,0(s0)
sw zero,4(s0)
sw s0,0(a5)
sub a4,s0,a5
sw a4,4(a5)
lla s4,start
sw a5,0(s4)
sw zero,4(s4)
sw a4,free_bytes_remaining,a5
sw a4,minimum_ever_free,a5
li a0,24
call heap_malloc_model
mv s2,a0
li a0,40
call heap_malloc_model
mv s3,a0
mv a0,s2
call heap_free_model
li a0,16
call heap_malloc_model
mv s1,a0
mv a0,s3
call heap_free_model
mv a0,s1
call heap_free_model
snez s2,s2
snez s3,s3
and s2,s2,s3
snez s1,s1
and s1,s1,s2
sw s1,g_allocations_ok,a5
lw a5,0(s4)
beq s0,a5,.L24
mv a4,a5
li a3,0
.L25:
addi a3,a3,1
lw a4,0(a4)
bne s0,a4,.L25
sw a3,g_final_free_nodes,a2
li a3,0
.L26:
lw a2,4(a5)
add a3,a3,a2
lw a5,0(a5)
bne a5,a4,.L26
.L27:
sw a3,g_final_free_total,a5
lw a5,minimum_ever_free
sw a5,g_minimum_ever_free,a4
li a0,0
lw ra,28(sp)
lw s0,24(sp)
lw s1,20(sp)
lw s2,16(sp)
lw s3,12(sp)
lw s4,8(sp)
addi sp,sp,32
jr ra
.L24:
sw zero,g_final_free_nodes,a5
li a3,0
j .L27
.size main, .-main
.globl g_minimum_ever_free
.globl g_final_free_total
.globl g_final_free_nodes
.globl g_allocations_ok
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 264
heap_area:
.zero 264
.section .sbss,"aw",@nobits
.align 2
.type minimum_ever_free, @object
.size minimum_ever_free, 4
minimum_ever_free:
.zero 4
.type free_bytes_remaining, @object
.size free_bytes_remaining, 4
free_bytes_remaining:
.zero 4
.type g_minimum_ever_free, @object
.size g_minimum_ever_free, 4
g_minimum_ever_free:
.zero 4
.type g_final_free_total, @object
.size g_final_free_total, 4
g_final_free_total:
.zero 4
.type g_final_free_nodes, @object
.size g_final_free_nodes, 4
g_final_free_nodes:
.zero 4
.type g_allocations_ok, @object
.size g_allocations_ok, 4
g_allocations_ok:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+31
View File
@@ -0,0 +1,31 @@
@startuml
scale 600 height
top to bottom direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 10
skinparam rectangleBorderColor #2c7794
skinparam rectangleBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A1 CONTEXT — from linear cursor to reusable heap
rectangle "<<application>>\n01 A / B / C experiment\nrequests payload bytes" as APP
rectangle "<<public API>>\n02 pvPortMalloc / vPortFree\nheap statistics" as API
rectangle "<<allocator>>\n03 heap_4\nfirst-fit · split · coalesce" as H4
rectangle "<<arena>>\n04 ucHeap[4096]\nheaders + payload" as ARENA
rectangle "<<target>>\n05 Hazard3 / RV32I\nGDB · registers · memory" as TARGET
APP -down-> API : request / release
API -down-> H4 : direct C call
H4 -down-> ARENA : manages
ARENA -down-> TARGET : stored in RAM
note right of H4
heap_1: allocate only
heap_2: free, no coalesce
heap_3: libc wrapper
heap_4: free + coalesce
heap_5: multiple regions
end note
@enduml
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="600.3488px" preserveAspectRatio="none" style="width:311px;height:600px;" version="1.1" viewBox="0 0 311 600" width="311.19px" zoomAndPan="magnify"><defs/><g><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="227.6552" x="44.9803" y="9.813">A1 CONTEXT — from linear cursor to reusable heap</text><!--MD5=[cd187d11655e4367e900de538d2f1d0c]
entity APP--><rect fill="#EDF6FA" height="55.8673" style="stroke: #2C7794; stroke-width: 1.3769468762104042;" width="121.1713" x="15.6054" y="20.2686"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="58.7497" x="24.785" y="39.2613">«application»</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="102.812" x="24.785" y="51.764">01 A / B / C experiment</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="102.812" x="24.785" y="64.2667">requests payload bytes</text><!--MD5=[645d8587175d8f7ad110cd370775931d]
entity API--><rect fill="#EDF6FA" height="55.8673" style="stroke: #2C7794; stroke-width: 1.3769468762104042;" width="141.3665" x="5.5078" y="144.9833"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="52.324" x="14.6874" y="163.976">«public API»</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="123.0073" x="14.6874" y="176.4787">02 pvPortMalloc / vPortFree</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="65.1755" x="14.6874" y="188.9813">heap statistics</text><!--MD5=[46e692d45a0b60d87b097c4043ce42a2]
entity H4--><rect fill="#EDF6FA" height="55.8673" style="stroke: #2C7794; stroke-width: 1.3769468762104042;" width="122.0893" x="15.1464" y="277.6108"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="48.6521" x="24.3261" y="296.6035">«allocator»</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="44.9803" x="24.3261" y="309.1062">03 heap_4</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="103.73" x="24.3261" y="321.6088">first-fit · split · coalesce</text><!--MD5=[a55d5013c595d66e4d3c908e0eed28a7]
entity ARENA--><rect fill="#EDF6FA" height="55.8673" style="stroke: #2C7794; stroke-width: 1.3769468762104042;" width="100.0581" x="26.162" y="410.2384"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="34.8827" x="35.3416" y="429.231">«arena»</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="74.3551" x="35.3416" y="441.7337">04 ucHeap[4096]</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="81.6988" x="35.3416" y="454.2364">headers + payload</text><!--MD5=[aba7613092619c8a5fef3de1c87efe89]
entity TARGET--><rect fill="#EDF6FA" height="55.8673" style="stroke: #2C7794; stroke-width: 1.3769468762104042;" width="129.433" x="11.4746" y="534.953"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="36.7186" x="20.6542" y="553.9457">«target»</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="84.4527" x="20.6542" y="566.4484">05 Hazard3 / RV32I</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="111.0737" x="20.6542" y="578.951">GDB · registers · memory</text><path d="M169.3645,269.698 L169.3645,301.8726 L137.6029,305.5445 L169.3645,309.2164 L169.3645,341.391 A0,0 0 0 0 169.3645,341.391 L300.6334,341.391 A0,0 0 0 0 300.6334,341.391 L300.6334,278.8776 L291.4538,269.698 L169.3645,269.698 A0,0 0 0 0 169.3645,269.698 " fill="#FBFB77" style="stroke: #A80036; stroke-width: 0.9179645841402695;"/><path d="M291.4538,269.698 L291.4538,278.8776 L300.6334,278.8776 L291.4538,269.698 " fill="#FBFB77" style="stroke: #A80036; stroke-width: 0.9179645841402695;"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="93.6324" x="174.8723" y="284.1008">heap_1: allocate only</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="111.9917" x="174.8723" y="296.6035">heap_2: free, no coalesce</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="91.7965" x="174.8723" y="309.1062">heap_3: libc wrapper</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="103.73" x="174.8723" y="321.6088">heap_4: free + coalesce</text><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="109.2378" x="174.8723" y="334.1115">heap_5: multiple regions</text><!--MD5=[ec942d3abfeef98a0155e2f031d4c131]
link APP to API--><path d="M76.1911,76.393 C76.1911,94.8441 76.1911,119.3262 76.1911,138.8972 " fill="none" id="APP-&gt;API" style="stroke: #8095A1; stroke-width: 0.9179645841402695;"/><polygon fill="#8095A1" points="76.1911,143.4136,79.8629,135.1519,76.1911,138.8238,72.5192,135.1519,76.1911,143.4136" style="stroke: #8095A1; stroke-width: 0.9179645841402695;"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="76.1911" x="77.109" y="114.4059">request / release</text><!--MD5=[798ded9ffd9ac635cdedeacba358c97c]
link API to H4--><path d="M76.1911,201.2545 C76.1911,221.6609 76.1911,249.6129 76.1911,271.3044 " fill="none" id="API-&gt;H4" style="stroke: #8095A1; stroke-width: 0.9179645841402695;"/><polygon fill="#8095A1" points="76.1911,275.8575,79.8629,267.5958,76.1911,271.2677,72.5192,267.5958,76.1911,275.8575" style="stroke: #8095A1; stroke-width: 0.9179645841402695;"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="52.324" x="77.109" y="239.1206">direct C call</text><!--MD5=[26d2234bc3ca60bb174d49bba6317b2c]
link H4 to ARENA--><path d="M76.1911,333.8821 C76.1911,354.2884 76.1911,382.2404 76.1911,403.9319 " fill="none" id="H4-&gt;ARENA" style="stroke: #8095A1; stroke-width: 0.9179645841402695;"/><polygon fill="#8095A1" points="76.1911,408.485,79.8629,400.2234,76.1911,403.8952,72.5192,400.2234,76.1911,408.485" style="stroke: #8095A1; stroke-width: 0.9179645841402695;"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="40.3904" x="77.109" y="379.661">manages</text><!--MD5=[057090cc36f41caea0c560281f72f0d5]
link ARENA to TARGET--><path d="M76.1911,466.3627 C76.1911,484.8138 76.1911,509.2959 76.1911,528.8669 " fill="none" id="ARENA-&gt;TARGET" style="stroke: #8095A1; stroke-width: 0.9179645841402695;"/><polygon fill="#8095A1" points="76.1911,533.3833,79.8629,525.1216,76.1911,528.7935,72.5192,525.1216,76.1911,533.3833" style="stroke: #8095A1; stroke-width: 0.9179645841402695;"/><text fill="#000000" font-family="Monospace" font-size="9.1796" lengthAdjust="spacingAndGlyphs" textLength="61.5036" x="77.109" y="504.3756">stored in RAM</text><!--MD5=[5f927a6771e8d55dac04e3c75dd184e1]
@startuml
scale 600 height
top to bottom direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 10
skinparam rectangleBorderColor #2c7794
skinparam rectangleBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A1 CONTEXT — from linear cursor to reusable heap
rectangle "<<application>>\n01 A / B / C experiment\nrequests payload bytes" as APP
rectangle "<<public API>>\n02 pvPortMalloc / vPortFree\nheap statistics" as API
rectangle "<<allocator>>\n03 heap_4\nfirst-fit · split · coalesce" as H4
rectangle "<<arena>>\n04 ucHeap[4096]\nheaders + payload" as ARENA
rectangle "<<target>>\n05 Hazard3 / RV32I\nGDB · registers · memory" as TARGET
APP -down-> API : request / release
API -down-> H4 : direct C call
H4 -down-> ARENA : manages
ARENA -down-> TARGET : stored in RAM
note right of H4
heap_1: allocate only
heap_2: free, no coalesce
heap_3: libc wrapper
heap_4: free + coalesce
heap_5: multiple regions
end note
@enduml
PlantUML version 1.2020.02(Sun Mar 01 11:22:07 CET 2020)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 25.0.4-ea+4-1-Debian
Operating System: Linux
Default Encoding: UTF-8
Language: en
Country: null
--></g></svg>

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+53
View File
@@ -0,0 +1,53 @@
@startuml
scale 620 height
top to bottom direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 10
skinparam classBorderColor #2c7794
skinparam classBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A2 STRUCTURE — RV32 header, split and two-sided coalescing
class "01 Block header" as HEADER {
next : BlockLink_t*
size : size_t
raw sizeof = 8 bytes on RV32
aligned xHeapStructSize = 16
}
class "Allocated block" as ALLOC {
02 A: header + 24 byte request
aligned total = 48 bytes
payload begins after header
}
class "03 Free remainder" as REM {
own header
free = 4032 bytes after A
}
HEADER *-- ALLOC : precedes payload
ALLOC -down-> REM : first-fit + split
class "04 Address-ordered free list" as LIST {
previous < inserted < current
A < B < C+tail
}
class "05 Previous / left neighbour" as LEFT {
previous + previous.size == inserted
merge previous first
}
class "06 Next / right neighbour" as RIGHT {
merged + merged.size == current
merge next second
}
REM -down-> LIST : insert by address
LIST -down-> LEFT : heap_4 tests first
LEFT -down-> RIGHT : then tests next
note right of LIST
Address order exposes both physical
neighbours without scanning the arena.
end note
@enduml
+76
View File
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="620.2694px" preserveAspectRatio="none" style="width:312px;height:620px;" version="1.1" viewBox="0 0 312 620" width="312.6158px" zoomAndPan="magnify"><defs/><g><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="211.9549" x="52.4571" y="7.5779">A2 STRUCTURE — RV32 header, split and two-sided coalescing</text><!--MD5=[9827fb78ff6e3f59cfadf959dc26af7c]
class HEADER--><rect fill="#EDF6FA" height="69.8104" id="HEADER" style="stroke: #2C7794; stroke-width: 1.0633189422886131;" width="107.7497" x="16.3042" y="15.1133"/><ellipse cx="40.1935" cy="25.0376" fill="#ADD1B2" rx="6.3799" ry="6.3799" style="stroke: #2C7794; stroke-width: 0.7088792948590754;"/><path d="M41.1017,27.3747 Q40.858,27.4965 40.5922,27.5519 Q40.3264,27.6184 40.0273,27.6184 Q38.9861,27.6184 38.4323,26.9316 Q37.8896,26.2449 37.8896,24.9379 Q37.8896,23.6309 38.4323,22.9442 Q38.9861,22.2575 40.0273,22.2575 Q40.3264,22.2575 40.5922,22.3239 Q40.8691,22.3793 41.1017,22.5011 L41.1017,23.642 Q40.8359,23.3983 40.5811,23.2876 Q40.3374,23.1768 40.0827,23.1768 Q39.5178,23.1768 39.2298,23.6198 Q38.9418,24.0629 38.9418,24.9379 Q38.9418,25.8129 39.2298,26.256 Q39.5178,26.699 40.0827,26.699 Q40.3374,26.699 40.5811,26.5883 Q40.8359,26.4775 41.1017,26.2338 L41.1017,27.3747 Z "/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="55.2926" x="51.9609" y="27.7881">01 Block header</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="17.0131" x2="123.345" y1="34.9619" y2="34.9619"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="63.0903" x="20.5575" y="45.3753">next : BlockLink_t*</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="38.9884" x="20.5575" y="55.0303">size : size_t</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="97.8253" x="20.5575" y="64.6852">raw sizeof = 8 bytes on RV32</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="99.2431" x="20.5575" y="74.3401">aligned xHeapStructSize = 16</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="17.0131" x2="123.345" y1="79.2527" y2="79.2527"/><!--MD5=[40d637119be40bcfdc4ce848efbdda9e]
class ALLOC--><rect fill="#EDF6FA" height="60.1555" id="ALLOC" style="stroke: #2C7794; stroke-width: 1.0633189422886131;" width="114.1296" x="13.1143" y="138.0897"/><ellipse cx="41.6467" cy="148.014" fill="#ADD1B2" rx="6.3799" ry="6.3799" style="stroke: #2C7794; stroke-width: 0.7088792948590754;"/><path d="M42.5549,150.3511 Q42.3112,150.4729 42.0454,150.5283 Q41.7796,150.5948 41.4805,150.5948 Q40.4393,150.5948 39.8855,149.908 Q39.3428,149.2213 39.3428,147.9143 Q39.3428,146.6073 39.8855,145.9206 Q40.4393,145.2339 41.4805,145.2339 Q41.7796,145.2339 42.0454,145.3003 Q42.3223,145.3557 42.5549,145.4775 L42.5549,146.6184 Q42.2891,146.3747 42.0343,146.2639 Q41.7906,146.1532 41.5359,146.1532 Q40.971,146.1532 40.683,146.5962 Q40.395,147.0393 40.395,147.9143 Q40.395,148.7893 40.683,149.2324 Q40.971,149.6754 41.5359,149.6754 Q41.7906,149.6754 42.0343,149.5647 Q42.2891,149.4539 42.5549,149.2102 L42.5549,150.3511 Z "/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="51.7482" x="54.052" y="150.7644">Allocated block</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="13.8231" x2="126.535" y1="157.9383" y2="157.9383"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="105.623" x="17.3675" y="168.3517">02 A: header + 24 byte request</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="81.5211" x="17.3675" y="178.0067">aligned total = 48 bytes</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="96.4076" x="17.3675" y="187.6616">payload begins after header</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="13.8231" x2="126.535" y1="192.5741" y2="192.5741"/><!--MD5=[71c2adef9be32489055f250a531e7c6f]
class REM--><rect fill="#EDF6FA" height="50.5005" id="REM" style="stroke: #2C7794; stroke-width: 1.0633189422886131;" width="92.8632" x="23.7475" y="251.4111"/><ellipse cx="37.4288" cy="261.3354" fill="#ADD1B2" rx="6.3799" ry="6.3799" style="stroke: #2C7794; stroke-width: 0.7088792948590754;"/><path d="M38.3371,263.6725 Q38.0934,263.7944 37.8276,263.8497 Q37.5617,263.9162 37.2627,263.9162 Q36.2215,263.9162 35.6677,263.2295 Q35.125,262.5427 35.125,261.2357 Q35.125,259.9287 35.6677,259.242 Q36.2215,258.5553 37.2627,258.5553 Q37.5617,258.5553 37.8276,258.6218 Q38.1045,258.6771 38.3371,258.799 L38.3371,259.9398 Q38.0712,259.6961 37.8165,259.5854 Q37.5728,259.4746 37.3181,259.4746 Q36.7532,259.4746 36.4652,259.9177 Q36.1772,260.3607 36.1772,261.2357 Q36.1772,262.1108 36.4652,262.5538 Q36.7532,262.9969 37.3181,262.9969 Q37.5728,262.9969 37.8165,262.8861 Q38.0712,262.7753 38.3371,262.5317 L38.3371,263.6725 Z "/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="63.0903" x="46.9278" y="264.0859">03 Free remainder</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="24.4563" x2="115.9018" y1="271.2597" y2="271.2597"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="40.4061" x="28.0007" y="281.6732">own header</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="84.3566" x="28.0007" y="291.3281">free = 4032 bytes after A</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="24.4563" x2="115.9018" y1="296.2406" y2="296.2406"/><!--MD5=[b0a0edd67d9eafaaa5639bbe6382adce]
class LIST--><rect fill="#EDF6FA" height="50.5005" id="LIST" style="stroke: #2C7794; stroke-width: 1.0633189422886131;" width="114.8384" x="12.7598" y="355.0776"/><ellipse cx="21.9753" cy="365.0019" fill="#ADD1B2" rx="6.3799" ry="6.3799" style="stroke: #2C7794; stroke-width: 0.7088792948590754;"/><path d="M22.8835,367.339 Q22.6398,367.4609 22.374,367.5162 Q22.1082,367.5827 21.8091,367.5827 Q20.7679,367.5827 20.2141,366.896 Q19.6714,366.2093 19.6714,364.9023 Q19.6714,363.5953 20.2141,362.9085 Q20.7679,362.2218 21.8091,362.2218 Q22.1082,362.2218 22.374,362.2883 Q22.6509,362.3436 22.8835,362.4655 L22.8835,363.6063 Q22.6177,363.3627 22.3629,363.2519 Q22.1192,363.1411 21.8645,363.1411 Q21.2996,363.1411 21.0116,363.5842 Q20.7236,364.0272 20.7236,364.9023 Q20.7236,365.7773 21.0116,366.2203 Q21.2996,366.6634 21.8645,366.6634 Q22.1192,366.6634 22.3629,366.5526 Q22.6177,366.4419 22.8835,366.1982 L22.8835,367.339 Z "/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="94.9898" x="30.4818" y="367.7524">04 Address-ordered free list</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="13.4687" x2="126.8894" y1="374.9263" y2="374.9263"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="99.2431" x="17.0131" y="385.3397">previous &lt; inserted &lt; current</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="46.0772" x="17.0131" y="394.9946">A &lt; B &lt; C+tail</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="13.4687" x2="126.8894" y1="399.9071" y2="399.9071"/><!--MD5=[007d3a1820cefd008c8dbdad7e6fdec6]
class LEFT--><rect fill="#EDF6FA" height="50.5005" id="LEFT" style="stroke: #2C7794; stroke-width: 1.0633189422886131;" width="131.8515" x="4.2533" y="458.7441"/><ellipse cx="21.1246" cy="468.6684" fill="#ADD1B2" rx="6.3799" ry="6.3799" style="stroke: #2C7794; stroke-width: 0.7088792948590754;"/><path d="M22.0329,471.0055 Q21.7892,471.1274 21.5233,471.1828 Q21.2575,471.2492 20.9585,471.2492 Q19.9173,471.2492 19.3635,470.5625 Q18.8207,469.8758 18.8207,468.5688 Q18.8207,467.2618 19.3635,466.575 Q19.9173,465.8883 20.9585,465.8883 Q21.2575,465.8883 21.5233,465.9548 Q21.8003,466.0102 22.0329,466.132 L22.0329,467.2728 Q21.767,467.0292 21.5123,466.9184 Q21.2686,466.8076 21.0138,466.8076 Q20.449,466.8076 20.161,467.2507 Q19.873,467.6937 19.873,468.5688 Q19.873,469.4438 20.161,469.8868 Q20.449,470.3299 21.0138,470.3299 Q21.2686,470.3299 21.5123,470.2191 Q21.767,470.1084 22.0329,469.8647 L22.0329,471.0055 Z "/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="94.9898" x="31.3325" y="471.4189">05 Previous / left neighbour</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="4.9622" x2="135.3959" y1="478.5928" y2="478.5928"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="123.345" x="8.5066" y="489.0062">previous + previous.size == inserted</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="68.7613" x="8.5066" y="498.6611">merge previous first</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="4.9622" x2="135.3959" y1="503.5736" y2="503.5736"/><!--MD5=[490fcd32b6546c9bc615869efabd094a]
class RIGHT--><rect fill="#EDF6FA" height="50.5005" id="RIGHT" style="stroke: #2C7794; stroke-width: 1.0633189422886131;" width="122.6361" x="8.861" y="562.4106"/><ellipse cx="25.0943" cy="572.335" fill="#ADD1B2" rx="6.3799" ry="6.3799" style="stroke: #2C7794; stroke-width: 0.7088792948590754;"/><path d="M26.0026,574.672 Q25.7589,574.7939 25.4931,574.8493 Q25.2272,574.9157 24.9282,574.9157 Q23.887,574.9157 23.3332,574.229 Q22.7905,573.5423 22.7905,572.2353 Q22.7905,570.9283 23.3332,570.2415 Q23.887,569.5548 24.9282,569.5548 Q25.2272,569.5548 25.4931,569.6213 Q25.77,569.6767 26.0026,569.7985 L26.0026,570.9394 Q25.7367,570.6957 25.482,570.5849 Q25.2383,570.4741 24.9836,570.4741 Q24.4187,570.4741 24.1307,570.9172 Q23.8427,571.3602 23.8427,572.2353 Q23.8427,573.1103 24.1307,573.5533 Q24.4187,573.9964 24.9836,573.9964 Q25.2383,573.9964 25.482,573.8856 Q25.7367,573.7749 26.0026,573.5312 L26.0026,574.672 Z "/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="87.1922" x="35.1604" y="575.0854">06 Next / right neighbour</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="9.5699" x2="130.7882" y1="582.2593" y2="582.2593"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="114.1296" x="13.1143" y="592.6727">merged + merged.size == current</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="65.2169" x="13.1143" y="602.3276">merge next second</text><line style="stroke: #2C7794; stroke-width: 1.0633189422886131;" x1="9.5699" x2="130.7882" y1="607.2402" y2="607.2402"/><path d="M152.409,367.1286 L152.409,377.4924 L127.9173,380.3279 L152.409,383.1634 L152.409,393.5272 A0,0 0 0 0 152.409,393.5272 L304.1092,393.5272 A0,0 0 0 0 304.1092,393.5272 L304.1092,374.2174 L297.0204,367.1286 L152.409,367.1286 A0,0 0 0 0 152.409,367.1286 " fill="#FBFB77" style="stroke: #A80036; stroke-width: 0.7088792948590754;"/><path d="M297.0204,367.1286 L297.0204,374.2174 L304.1092,374.2174 L297.0204,367.1286 " fill="#FBFB77" style="stroke: #A80036; stroke-width: 0.7088792948590754;"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="124.7628" x="156.6623" y="378.2509">Address order exposes both physical</text><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="136.8137" x="156.6623" y="387.9058">neighbours without scanning the arena.</text><!--MD5=[fc2657b7de304bb41a5d36ac60748945]
reverse link HEADER to ALLOC--><path d="M70.1791,95.245 C70.1791,109.5006 70.1791,124.9329 70.1791,137.8557 " fill="none" id="HEADER&lt;-ALLOC" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><polygon fill="#8095A1" points="70.1791,86.1926,67.3435,90.4459,70.1791,94.6992,73.0146,90.4459,70.1791,86.1926" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="60.2547" x="70.8879" y="114.4769">precedes payload</text><!--MD5=[bb73b5a7e21afff1a1d0c43dcbd5d11a]
link ALLOC to REM--><path d="M70.1791,198.4224 C70.1791,213.4506 70.1791,231.6688 70.1791,246.6687 " fill="none" id="ALLOC-&gt;REM" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><polygon fill="#8095A1" points="70.1791,250.1422,73.0146,243.7623,70.1791,246.5978,67.3435,243.7623,70.1791,250.1422" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="46.0772" x="70.8879" y="227.7983">first-fit + split</text><!--MD5=[32aec76f28e14f88b0d74d9f592c80fb]
link REM to LIST--><path d="M70.1791,302.1243 C70.1791,316.5855 70.1791,334.9242 70.1791,350.1297 " fill="none" id="REM-&gt;LIST" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><polygon fill="#8095A1" points="70.1791,353.667,73.0146,347.287,70.1791,350.1226,67.3435,347.287,70.1791,353.667" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="58.837" x="70.8879" y="331.4649">insert by address</text><!--MD5=[cc3605e5f4ba919f564e1d79e1300518]
link LIST to LEFT--><path d="M70.1791,405.7909 C70.1791,420.252 70.1791,438.5907 70.1791,453.7962 " fill="none" id="LIST-&gt;LEFT" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><polygon fill="#8095A1" points="70.1791,457.3335,73.0146,450.9536,70.1791,453.7891,67.3435,450.9536,70.1791,457.3335" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="58.837" x="70.8879" y="435.1314">heap_4 tests first</text><!--MD5=[e8df39c6ef374d140d6cfa6fe0b5ac87]
link LEFT to RIGHT--><path d="M70.1791,509.4574 C70.1791,523.9185 70.1791,542.2572 70.1791,557.4627 " fill="none" id="LEFT-&gt;RIGHT" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><polygon fill="#8095A1" points="70.1791,561,73.0146,554.6201,70.1791,557.4556,67.3435,554.6201,70.1791,561" style="stroke: #8095A1; stroke-width: 0.7088792948590754;"/><text fill="#000000" font-family="Monospace" font-size="7.0888" lengthAdjust="spacingAndGlyphs" textLength="51.7482" x="70.8879" y="538.7979">then tests next</text><!--MD5=[d8b34fb36bd2f73cd895320cbfc22e63]
@startuml
scale 620 height
top to bottom direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 10
skinparam classBorderColor #2c7794
skinparam classBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A2 STRUCTURE — RV32 header, split and two-sided coalescing
class "01 Block header" as HEADER {
next : BlockLink_t*
size : size_t
raw sizeof = 8 bytes on RV32
aligned xHeapStructSize = 16
}
class "Allocated block" as ALLOC {
02 A: header + 24 byte request
aligned total = 48 bytes
payload begins after header
}
class "03 Free remainder" as REM {
own header
free = 4032 bytes after A
}
HEADER *- - ALLOC : precedes payload
ALLOC -down-> REM : first-fit + split
class "04 Address-ordered free list" as LIST {
previous < inserted < current
A < B < C+tail
}
class "05 Previous / left neighbour" as LEFT {
previous + previous.size == inserted
merge previous first
}
class "06 Next / right neighbour" as RIGHT {
merged + merged.size == current
merge next second
}
REM -down-> LIST : insert by address
LIST -down-> LEFT : heap_4 tests first
LEFT -down-> RIGHT : then tests next
note right of LIST
Address order exposes both physical
neighbours without scanning the arena.
end note
@enduml
PlantUML version 1.2020.02(Sun Mar 01 11:22:07 CET 2020)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 25.0.4-ea+4-1-Debian
Operating System: Linux
Default Encoding: UTF-8
Language: en
Country: null
--></g></svg>

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+29
View File
@@ -0,0 +1,29 @@
@startuml
scale 820 width
top to bottom direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 9
skinparam rectangleBorderColor #2c7794
skinparam rectangleBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A5 FLOW — eight deterministic Hazard3 replay states
rectangle "01 E01 RESET\n1 block · free=4080\nraw header=8 · aligned=16" as S1
rectangle "02 E02 ALLOC A(24)\nconsumed=48\nfree=4032" as S2
rectangle "03 E03 ALLOC B(40)\nconsumed=64\nfree=3968" as S3
rectangle "04 E04 ALLOC C(16)\nconsumed=32\nfree=3936" as S4
rectangle "05 E05 FREE A\n2 free blocks\nfree=3984" as S5
rectangle "06 E06 FREE C\n2 blocks · fragmented\nfree=4016" as S6
rectangle "07 E07 FREE B\nmerge left + right\n1 block · free=4080" as S7
rectangle "08 E08 OOM / VERIFY\nNULL · hook=1\nasserts=0 · PASS=1" as S8
S1 -right-> S2 : pvPortMalloc
S2 -right-> S3 : split remainder
S3 -right-> S4 : split remainder
S4 -down-> S5 : vPortFree(A)
S5 -left-> S6 : vPortFree(C)
S6 -left-> S7 : vPortFree(B)
S7 -left-> S8 : oversized request
@enduml
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="236.6008px" preserveAspectRatio="none" style="width:812px;height:236px;" version="1.1" viewBox="0 0 812 236" width="812.4374px" zoomAndPan="magnify"><defs/><g><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="239.8419" x="290.0791" y="10.3943">A5 FLOW — eight deterministic Hazard3 replay states</text><!--MD5=[d0cae5ff9305becd6f502d4fe4f41481]
entity S1--><rect fill="#EDF6FA" height="61.3371" style="stroke: #2C7794; stroke-width: 1.6205533596837944;" width="142.6087" x="6.4822" y="22.3724"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="59.4203" x="17.2859" y="43.5704">01 E01 RESET</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="83.1884" x="17.2859" y="56.8136">1 block · free=4080</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="121.0013" x="17.2859" y="70.0569">raw header=8 · aligned=16</text><!--MD5=[d3395af3100fe8cebf35265100c4415f]
entity S2--><rect fill="#EDF6FA" height="61.3371" style="stroke: #2C7794; stroke-width: 1.6205533596837944;" width="110.1976" x="247.4045" y="22.3724"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="88.5903" x="258.2082" y="43.5704">02 E02 ALLOC A(24)</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="63.7418" x="258.2082" y="56.8136">consumed=48</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="45.3755" x="258.2082" y="70.0569">free=4032</text><!--MD5=[6c7192638421eaf134d201765ac5db29]
entity S3--><rect fill="#EDF6FA" height="61.3371" style="stroke: #2C7794; stroke-width: 1.6205533596837944;" width="110.1976" x="466.7194" y="22.3724"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="88.5903" x="477.5231" y="43.5704">03 E03 ALLOC B(40)</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="63.7418" x="477.5231" y="56.8136">consumed=64</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="45.3755" x="477.5231" y="70.0569">free=3968</text><!--MD5=[01b905ebe31ca24275b86a9951c478d0]
entity S4--><rect fill="#EDF6FA" height="61.3371" style="stroke: #2C7794; stroke-width: 1.6205533596837944;" width="110.1976" x="686.0343" y="22.3724"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="88.5903" x="696.8379" y="43.5704">04 E04 ALLOC C(16)</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="63.7418" x="696.8379" y="56.8136">consumed=32</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="45.3755" x="696.8379" y="70.0569">free=3936</text><!--MD5=[099bd2cf1f4f3a6dbf2f1fde6174f38e]
entity S5--><rect fill="#EDF6FA" height="61.3371" style="stroke: #2C7794; stroke-width: 1.6205533596837944;" width="84.2688" x="698.9987" y="163.663"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="62.6614" x="709.8024" y="184.861">05 E05 FREE A</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="56.1792" x="709.8024" y="198.1043">2 free blocks</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="45.3755" x="709.8024" y="211.3475">free=3984</text><!--MD5=[0656c7721b015b9ca8cf2e2c2e8f5aee]
entity S6--><rect fill="#EDF6FA" height="61.3371" style="stroke: #2C7794; stroke-width: 1.6205533596837944;" width="118.8406" x="484.0053" y="163.663"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="62.6614" x="494.809" y="184.861">06 E06 FREE C</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="97.2332" x="494.809" y="198.1043">2 blocks · fragmented</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="45.3755" x="494.809" y="211.3475">free=4016</text><!--MD5=[6ba532df225878998c5769132c3837e2]
entity S7--><rect fill="#EDF6FA" height="61.3371" style="stroke: #2C7794; stroke-width: 1.6205533596837944;" width="104.7958" x="282.5165" y="163.663"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="62.6614" x="293.3202" y="184.861">07 E07 FREE B</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="78.8669" x="293.3202" y="198.1043">merge left + right</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="83.1884" x="293.3202" y="211.3475">1 block · free=4080</text><!--MD5=[ce611cd767df6ac8a45071985dabb378]
entity S8--><rect fill="#EDF6FA" height="61.3371" style="stroke: #2C7794; stroke-width: 1.6205533596837944;" width="115.5995" x="46.996" y="163.663"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="93.9921" x="57.7997" y="184.861">08 E08 OOM / VERIFY</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="65.9025" x="57.7997" y="198.1043">NULL · hook=1</text><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="82.108" x="57.7997" y="211.3475">asserts=0 · PASS=1</text><!--MD5=[15cb110786dd224ca6a510a3d2a24e20]
link S1 to S2--><path d="M149.5014,53.044 C178.639,53.044 211.9792,53.044 240.0796,53.044 " fill="none" id="S1-&gt;S2" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><polygon fill="#8095A1" points="245.4706,53.044,235.7473,48.7226,240.0688,53.044,235.7473,57.3655,245.4706,53.044" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="58.3399" x="169.0777" y="46.1524">pvPortMalloc</text><!--MD5=[8eac89d31ced3c9003fbd6a8a005c745]
link S2 to S3--><path d="M358.1315,53.044 C389.0516,53.044 427.6208,53.044 459.5457,53.044 " fill="none" id="S2-&gt;S3" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><polygon fill="#8095A1" points="464.6126,53.044,454.8893,48.7226,459.2108,53.044,454.8893,57.3655,464.6126,53.044" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="69.1436" x="377.5889" y="46.1524">split remainder</text><!--MD5=[82e284a09e4ebade00512bdc77bff32c]
link S3 to S4--><path d="M577.4464,53.044 C608.3665,53.044 646.9357,53.044 678.8606,53.044 " fill="none" id="S3-&gt;S4" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><polygon fill="#8095A1" points="683.9275,53.044,674.2042,48.7226,678.5257,53.044,674.2042,57.3655,683.9275,53.044" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="69.1436" x="596.9038" y="46.1524">split remainder</text><!--MD5=[0e6b031b378ab8978eaa734a548bdfb6]
link S4 to S5--><path d="M741.1331,83.9642 C741.1331,105.2367 741.1331,133.9637 741.1331,156.6298 " fill="none" id="S4-&gt;S5" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><polygon fill="#8095A1" points="741.1331,161.8156,745.4545,152.0923,741.1331,156.4137,736.8116,152.0923,741.1331,161.8156" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="57.2596" x="742.2134" y="127.6015">vPortFree(A)</text><!--MD5=[3c9aaad44b43c509ca535c241fdd0151]
reverse link S6 to S5--><path d="M610.2356,194.3239 C639.492,194.3239 672.9618,194.3239 698.7286,194.3239 " fill="none" id="S6&lt;-S5" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><polygon fill="#8095A1" points="604.9202,194.3239,614.6435,198.6454,610.322,194.3239,614.6435,190.0024,604.9202,194.3239" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="57.2596" x="622.2925" y="187.4323">vPortFree(C)</text><!--MD5=[5f6ea4567bb0950bff1d27387d49b4c2]
reverse link S7 to S6--><path d="M394.7668,194.3239 C422.6403,194.3239 455.7212,194.3239 483.5947,194.3239 " fill="none" id="S7&lt;-S6" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><polygon fill="#8095A1" points="389.4406,194.3239,399.1639,198.6454,394.8424,194.3239,399.1639,190.0024,389.4406,194.3239" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="57.2596" x="407.029" y="187.4323">vPortFree(B)</text><!--MD5=[630b9631f886cf9c8b0d992525b86cf3]
reverse link S8 to S7--><path d="M169.8448,194.3239 C205.3025,194.3239 248.6253,194.3239 281.9871,194.3239 " fill="none" id="S8&lt;-S7" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><polygon fill="#8095A1" points="164.497,194.3239,174.2203,198.6454,169.8988,194.3239,174.2203,190.0024,164.497,194.3239" style="stroke: #8095A1; stroke-width: 1.080368906455863;"/><text fill="#000000" font-family="Monospace" font-size="9.7233" lengthAdjust="spacingAndGlyphs" textLength="81.0277" x="182.0422" y="187.4323">oversized request</text><!--MD5=[9f7e06700e17220ce72e8c57ed6bc819]
@startuml
scale 820 width
top to bottom direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 9
skinparam rectangleBorderColor #2c7794
skinparam rectangleBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A5 FLOW — eight deterministic Hazard3 replay states
rectangle "01 E01 RESET\n1 block · free=4080\nraw header=8 · aligned=16" as S1
rectangle "02 E02 ALLOC A(24)\nconsumed=48\nfree=4032" as S2
rectangle "03 E03 ALLOC B(40)\nconsumed=64\nfree=3968" as S3
rectangle "04 E04 ALLOC C(16)\nconsumed=32\nfree=3936" as S4
rectangle "05 E05 FREE A\n2 free blocks\nfree=3984" as S5
rectangle "06 E06 FREE C\n2 blocks · fragmented\nfree=4016" as S6
rectangle "07 E07 FREE B\nmerge left + right\n1 block · free=4080" as S7
rectangle "08 E08 OOM / VERIFY\nNULL · hook=1\nasserts=0 · PASS=1" as S8
S1 -right-> S2 : pvPortMalloc
S2 -right-> S3 : split remainder
S3 -right-> S4 : split remainder
S4 -down-> S5 : vPortFree(A)
S5 -left-> S6 : vPortFree(C)
S6 -left-> S7 : vPortFree(B)
S7 -left-> S8 : oversized request
@enduml
PlantUML version 1.2020.02(Sun Mar 01 11:22:07 CET 2020)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 25.0.4-ea+4-1-Debian
Operating System: Linux
Default Encoding: UTF-8
Language: en
Country: null
--></g></svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+29
View File
@@ -0,0 +1,29 @@
@startuml
scale 600 height
top to bottom direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 10
skinparam stateBorderColor #2c7794
skinparam stateBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A6 STATE — reusable states of the same RV32 arena
[*] --> Free : 01 E01 one list node
Free --> Allocated : 02 E04 A+B+C allocated\ncurrent free = 3936
Allocated --> Fragmented : E05 free A\nE06 free C
Fragmented : 03 E06 two free blocks
Fragmented : sum free > largest block
Fragmented --> Coalesced : 04 E07 free B\nmerge previous, then next
Coalesced : one free block
Coalesced : current = largest = initial
Coalesced --> OOMChecked : 05 E08 request > arena
OOMChecked : NULL · hook=1 · PASS=1
OOMChecked --> [*]
state "minimum-ever" as MIN
Allocated -right-> MIN : records low watermark
Fragmented -right-> MIN : does not rise
Coalesced -right-> MIN : history remains
@enduml
+50
View File
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="600.2775px" preserveAspectRatio="none" style="width:321px;height:600px;" version="1.1" viewBox="0 0 321 600" width="321.3164px" zoomAndPan="magnify"><defs/><g><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="184.7569" x="70.8357" y="7.8065">A6 STATE — reusable states of the same RV32 arena</text><ellipse cx="193.155" cy="23.2954" fill="#000000" rx="7.3026" ry="7.3026" style="stroke: none; stroke-width: 0.7302646103274287;"/><rect fill="#EDF6FA" height="36.5132" rx="9.1283" ry="9.1283" style="stroke: #2C7794; stroke-width: 1.0953969154911432;" width="36.5132" x="174.8984" y="85.3679"/><line style="stroke: #2C7794; stroke-width: 1.0953969154911432;" x1="174.8984" x2="211.4116" y1="102.6168" y2="102.6168"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="15.3356" x="185.4872" y="96.8258">Free</text><rect fill="#EDF6FA" height="36.5132" rx="9.1283" ry="9.1283" style="stroke: #2C7794; stroke-width: 1.0953969154911432;" width="47.4672" x="169.4214" y="186.8747"/><line style="stroke: #2C7794; stroke-width: 1.0953969154911432;" x1="169.4214" x2="216.8886" y1="204.1235" y2="204.1235"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="32.8619" x="176.724" y="198.3325">Allocated</text><rect fill="#EDF6FA" height="44.4439" rx="9.1283" ry="9.1283" style="stroke: #2C7794; stroke-width: 1.0953969154911432;" width="99.316" x="93.1087" y="288.3815"/><line style="stroke: #2C7794; stroke-width: 1.0953969154911432;" x1="93.1087" x2="192.4247" y1="305.6303" y2="305.6303"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="42.3553" x="121.5891" y="299.8393">Fragmented</text><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="79.5988" x="96.7601" y="317.0882">03 E06 two free blocks</text><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="84.7107" x="96.7601" y="327.0344">sum free &gt; largest block</text><rect fill="#EDF6FA" height="44.4439" rx="9.1283" ry="9.1283" style="stroke: #2C7794; stroke-width: 1.0953969154911432;" width="102.9673" x="4.3816" y="397.8189"/><line style="stroke: #2C7794; stroke-width: 1.0953969154911432;" x1="4.3816" x2="107.3489" y1="415.0678" y2="415.0678"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="35.783" x="37.9738" y="409.2768">Coalesced</text><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="49.658" x="8.0329" y="426.5256">one free block</text><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="88.362" x="8.0329" y="436.4718">current = largest = initial</text><rect fill="#EDF6FA" height="36.5132" rx="9.1283" ry="9.1283" style="stroke: #2C7794; stroke-width: 1.0953969154911432;" width="96.3949" x="7.6678" y="497.0327"/><line style="stroke: #2C7794; stroke-width: 1.0953969154911432;" x1="7.6678" x2="104.0627" y1="514.2815" y2="514.2815"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="47.4672" x="32.1316" y="508.4905">OOMChecked</text><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="81.7896" x="11.3191" y="525.7394">NULL · hook=1 · PASS=1</text><ellipse cx="55.8652" cy="585.3947" fill="none" rx="7.3026" ry="7.3026" style="stroke: #000000; stroke-width: 0.7302646103274287;"/><ellipse cx="56.2304" cy="585.7598" fill="#000000" rx="4.3816" ry="4.3816" style="stroke: none; stroke-width: 0.7302646103274287;"/><rect fill="#EDF6FA" height="36.5132" rx="9.1283" ry="9.1283" style="stroke: #2C7794; stroke-width: 1.0953969154911432;" width="64.9936" x="188.4083" y="401.7843"/><line style="stroke: #2C7794; stroke-width: 1.0953969154911432;" x1="188.4083" x2="253.4018" y1="419.0331" y2="419.0331"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="50.3883" x="195.7109" y="413.2421">minimum-ever</text><!--MD5=[f16808fa220dde377303612249059854]
link *start to Free--><path d="M193.155,30.7514 C193.155,41.647 193.155,63.6352 193.155,80.4898 " fill="none" id="*start-&gt;Free" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><polygon fill="#8095A1" points="193.155,83.9877,196.076,77.4153,193.155,80.3364,190.2339,77.4153,193.155,83.9877" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="72.2962" x="193.8853" y="61.0428">01 E01 one list node</text><!--MD5=[19abaf48dc778410378e95e0d1bca006]
link Free to Allocated--><path d="M193.155,122.239 C193.155,138.9547 193.155,163.9809 193.155,182.0111 " fill="none" id="Free-&gt;Allocated" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><polygon fill="#8095A1" points="193.155,185.5675,196.076,178.9951,193.155,181.9162,190.2339,178.9951,193.155,185.5675" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="83.9804" x="193.8853" y="152.3259">02 E04 A+B+C allocated</text><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="67.9146" x="201.9182" y="162.2721">current free = 3936</text><!--MD5=[f6fa3e1917b73de38ea03346a6903b17]
link Allocated to Fragmented--><path d="M184.4941,223.7385 C176.4611,240.3228 164.4191,265.181 155.3346,283.9342 " fill="none" id="Allocated-&gt;Fragmented" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><polygon fill="#8095A1" points="153.8083,287.0889,159.2954,282.4391,155.3952,283.8004,154.0338,279.9002,153.8083,287.0889" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="35.783" x="174.6866" y="253.8327">E05 free A</text><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="35.783" x="174.6866" y="263.7789">E06 free C</text><!--MD5=[c73bca393b062d6a86dff80fa900c78c]
link Fragmented to Coalesced--><path d="M92.7509,329.6414 C81.6363,335.9582 71.0474,344.1883 63.8982,354.7333 C56.391,365.8114 54.1053,380.4606 53.8278,393.0065 " fill="none" id="Fragmented-&gt;Coalesced" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><polygon fill="#8095A1" points="53.8132,396.4241,56.753,389.8601,53.8236,392.7728,50.9109,389.8434,53.8132,396.4241" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="47.4672" x="87.2666" y="363.2701">04 E07 free B</text><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="92.7436" x="64.6284" y="373.2163">merge previous, then next</text><!--MD5=[2b306524d92469efb3bb270d490680cf]
link Coalesced to OOMChecked--><path d="M55.8652,442.5257 C55.8652,457.3647 55.8652,476.9796 55.8652,491.9939 " fill="none" id="Coalesced-&gt;OOMChecked" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><polygon fill="#8095A1" points="55.8652,495.5941,58.7863,489.0217,55.8652,491.9427,52.9442,489.0217,55.8652,495.5941" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="82.5199" x="56.5955" y="472.7076">05 E08 request &gt; arena</text><!--MD5=[6b8be85e17f6174335a5329fe1414850]
link OOMChecked to *end--><path d="M55.8652,533.6993 C55.8652,546.2379 55.8652,562.6835 55.8652,573.4622 " fill="none" id="OOMChecked-&gt;*end" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><polygon fill="#8095A1" points="55.8652,576.8725,58.7863,570.3001,55.8652,573.2212,52.9442,570.3001,55.8652,576.8725" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><!--MD5=[b09143a670c8c67aa163b99fca5c6b6b]
link Allocated to MIN--><path d="M206.402,223.6581 C210.5791,230.1502 214.7489,237.7595 217.2537,245.2959 C235.6199,300.555 231.8517,317.8477 227.4774,375.911 C226.9589,382.7609 225.995,390.1731 224.958,396.8988 " fill="none" id="Allocated-&gt;MIN" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><polygon fill="#8095A1" points="224.403,400.3384,228.3223,394.3078,224.9778,396.7326,222.5531,393.3881,224.403,400.3384" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="81.0594" x="231.8006" y="313.6632">records low watermark</text><!--MD5=[1fa23b8554d20d5dd3f2f8930c0e2df3]
link Fragmented to MIN--><path d="M152.2237,332.9422 C158.409,345.9702 167.1649,362.5618 177.0892,375.911 C182.9094,383.7394 190.1317,391.4875 197.0254,398.2279 " fill="none" id="Fragmented-&gt;MIN" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><polygon fill="#8095A1" points="199.5813,400.6889,196.8641,394.0296,196.9477,398.1598,192.8176,398.2434,199.5813,400.6889" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="46.0067" x="177.8194" y="368.382">does not rise</text><!--MD5=[87522a40b34d64237ec6603d41372cd8]
link Coalesced to MIN--><path d="M107.6337,420.0409 C132.2582,420.0409 161.1402,420.0409 183.5812,420.0409 " fill="none" id="Coalesced-&gt;MIN" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><polygon fill="#8095A1" points="187.1595,420.0409,180.5871,417.1198,183.5082,420.0409,180.5871,422.962,187.1595,420.0409" style="stroke: #8095A1; stroke-width: 0.7302646103274287;"/><text fill="#000000" font-family="Monospace" font-size="7.3026" lengthAdjust="spacingAndGlyphs" textLength="54.7698" x="120.4937" y="415.4329">history remains</text><!--MD5=[ca62bd85e5915d2f6639cfa0fa77f8b6]
@startuml
scale 600 height
top to bottom direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 10
skinparam stateBorderColor #2c7794
skinparam stateBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A6 STATE — reusable states of the same RV32 arena
[*] - -> Free : 01 E01 one list node
Free - -> Allocated : 02 E04 A+B+C allocated\ncurrent free = 3936
Allocated - -> Fragmented : E05 free A\nE06 free C
Fragmented : 03 E06 two free blocks
Fragmented : sum free > largest block
Fragmented - -> Coalesced : 04 E07 free B\nmerge previous, then next
Coalesced : one free block
Coalesced : current = largest = initial
Coalesced - -> OOMChecked : 05 E08 request > arena
OOMChecked : NULL · hook=1 · PASS=1
OOMChecked - -> [*]
state "minimum-ever" as MIN
Allocated -right-> MIN : records low watermark
Fragmented -right-> MIN : does not rise
Coalesced -right-> MIN : history remains
@enduml
PlantUML version 1.2020.02(Sun Mar 01 11:22:07 CET 2020)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 25.0.4-ea+4-1-Debian
Operating System: Linux
Default Encoding: UTF-8
Language: en
Country: null
--></g></svg>

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+23
View File
@@ -0,0 +1,23 @@
@startuml
scale 820 width
left to right direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 9
skinparam rectangleBorderColor #2c7794
skinparam rectangleBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A7 RUNTIME — source identity, replay, memory and verdict
rectangle "<<source>>\n01 task03 C\nA/B/C experiment" as SRC
rectangle "<<artifact>>\n02 ELF + image\nheap_4 symbols" as ELF
rectangle "<<checkpoint E06>>\n03 fragmented\nblocks=2 · free>largest" as E1
rectangle "<<memory>>\n04 ucHeap[4096]\nheaders + payload" as MEM
rectangle "<<checkpoint E08>>\n05 verified\nfinal=initial · blocks=1\nOOM=1 · PASS=1" as E2
SRC --> ELF : compile + link
ELF --> E1 : reset / load / replay
E1 --> MEM : inspect bytes
MEM --> E2 : continue
@enduml
+44
View File
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="82px" preserveAspectRatio="none" style="width:814px;height:82px;" version="1.1" viewBox="0 0 814 82" width="814.26px" zoomAndPan="magnify"><defs/><g><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="204.18" x="307.91" y="7.8893">A7 RUNTIME — source identity, replay, memory and verdict</text><!--MD5=[183d75e42a7a852d02a7f9ce2cc3343f]
entity SRC--><rect fill="#EDF6FA" height="46.5549" style="stroke: #2C7794; stroke-width: 1.23;" width="77.9" x="4.92" y="21.6136"/><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="31.16" x="13.12" y="37.7029">«source»</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="38.54" x="13.12" y="47.7545">01 task03 C</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="61.5" x="13.12" y="57.8062">A/B/C experiment</text><!--MD5=[4a7b087cd571ae358eb895b9dc7ce0ed]
entity ELF--><rect fill="#EDF6FA" height="46.5549" style="stroke: #2C7794; stroke-width: 1.23;" width="71.34" x="179.58" y="21.6136"/><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="31.98" x="187.78" y="37.7029">«artifact»</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="50.84" x="187.78" y="47.7545">02 ELF + image</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="54.94" x="187.78" y="57.8062">heap_4 symbols</text><!--MD5=[45565a74ca8123df0b8413ce5340d40a]
entity E1--><rect fill="#EDF6FA" height="46.5549" style="stroke: #2C7794; stroke-width: 1.23;" width="92.66" x="367.36" y="21.6136"/><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="59.86" x="375.56" y="37.7029">«checkpoint E06»</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="51.66" x="375.56" y="47.7545">03 fragmented</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="76.26" x="375.56" y="57.8062">blocks=2 · free&gt;largest</text><!--MD5=[5ceda9279414f57783e95166b8d927b7]
entity MEM--><rect fill="#EDF6FA" height="46.5549" style="stroke: #2C7794; stroke-width: 1.23;" width="80.36" x="555.96" y="21.6136"/><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="36.9" x="564.16" y="37.7029">«memory»</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="58.22" x="564.16" y="47.7545">04 ucHeap[4096]</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="63.96" x="564.16" y="57.8062">headers + payload</text><!--MD5=[a3c437a7ee715155cf6a0fec80d7cbaf]
entity E2--><rect fill="#EDF6FA" height="56.6065" style="stroke: #2C7794; stroke-width: 1.23;" width="87.74" x="717.5" y="16.587"/><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="59.86" x="725.7" y="32.6763">«checkpoint E08»</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="36.08" x="725.7" y="42.7279">05 verified</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="71.34" x="725.7" y="52.7796">final=initial · blocks=1</text><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="56.58" x="725.7" y="62.8312">OOM=1 · PASS=1</text><!--MD5=[2e73c4d4efa4a7715d66b06010e70f85]
link SRC to ELF--><path d="M83.2054,44.8852 C110.372,44.8852 146.6734,44.8852 174.209,44.8852 " fill="none" id="SRC-&gt;ELF" style="stroke: #8095A1; stroke-width: 0.82;"/><polygon fill="#8095A1" points="178.0056,44.8852,170.6256,41.6052,173.9056,44.8852,170.6256,48.1652,178.0056,44.8852" style="stroke: #8095A1; stroke-width: 0.82;"/><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="45.92" x="108.24" y="42.1145">compile + link</text><!--MD5=[98439561a91d683361f6e8599ebb304f]
link ELF to E1--><path d="M251.2562,44.8852 C282.0636,44.8852 327.221,44.8852 361.9726,44.8852 " fill="none" id="ELF-&gt;E1" style="stroke: #8095A1; stroke-width: 0.82;"/><polygon fill="#8095A1" points="365.9988,44.8852,358.6188,41.6052,361.8988,44.8852,358.6188,48.1652,365.9988,44.8852" style="stroke: #8095A1; stroke-width: 0.82;"/><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="65.6" x="276.34" y="42.1145">reset / load / replay</text><!--MD5=[14b84038a04448f34eb55bd119f2d935]
link E1 to MEM--><path d="M460.3726,44.8852 C488.0476,44.8852 523.1518,44.8852 550.6136,44.8852 " fill="none" id="E1-&gt;MEM" style="stroke: #8095A1; stroke-width: 0.82;"/><polygon fill="#8095A1" points="554.4102,44.8852,547.0302,41.6052,550.3102,44.8852,547.0302,48.1652,554.4102,44.8852" style="stroke: #8095A1; stroke-width: 0.82;"/><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="45.1" x="485.44" y="42.1145">inspect bytes</text><!--MD5=[28f0a5a321106494a7afb95cbc1e35b0]
link MEM to E2--><path d="M636.5578,44.8852 C659.3374,44.8852 688.1276,44.8852 712.2274,44.8852 " fill="none" id="MEM-&gt;E2" style="stroke: #8095A1; stroke-width: 0.82;"/><polygon fill="#8095A1" points="716.0568,44.8852,708.6768,41.6052,711.9568,44.8852,708.6768,48.1652,716.0568,44.8852" style="stroke: #8095A1; stroke-width: 0.82;"/><text fill="#000000" font-family="Monospace" font-size="7.38" lengthAdjust="spacingAndGlyphs" textLength="30.34" x="661.74" y="42.1145">continue</text><!--MD5=[d3cc7590ea240485cc9ae283b10caa4d]
@startuml
scale 820 width
left to right direction
skinparam backgroundColor transparent
skinparam shadowing false
skinparam defaultFontName Monospace
skinparam defaultFontSize 9
skinparam rectangleBorderColor #2c7794
skinparam rectangleBackgroundColor #edf6fa
skinparam ArrowColor #8095a1
title A7 RUNTIME — source identity, replay, memory and verdict
rectangle "<<source>>\n01 task03 C\nA/B/C experiment" as SRC
rectangle "<<artifact>>\n02 ELF + image\nheap_4 symbols" as ELF
rectangle "<<checkpoint E06>>\n03 fragmented\nblocks=2 · free>largest" as E1
rectangle "<<memory>>\n04 ucHeap[4096]\nheaders + payload" as MEM
rectangle "<<checkpoint E08>>\n05 verified\nfinal=initial · blocks=1\nOOM=1 · PASS=1" as E2
SRC - -> ELF : compile + link
ELF - -> E1 : reset / load / replay
E1 - -> MEM : inspect bytes
MEM - -> E2 : continue
@enduml
PlantUML version 1.2020.02(Sun Mar 01 11:22:07 CET 2020)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 25.0.4-ea+4-1-Debian
Operating System: Linux
Default Encoding: UTF-8
Language: en
Country: null
--></g></svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

+190
View File
@@ -0,0 +1,190 @@
# FC01 — FreeRTOS `heap_4`: od kursora do wolnych bloków
## Decyzja dydaktyczna
To jest jedna 30-minutowa lekcja i bezpośrednia kontynuacja projektu
K&R 5.4 z karty C07. Karta nie uczy pięciu implementacji heap jako pięciu
równorzędnych tematów. `heap_1`, `heap_2`, `heap_3` i `heap_5` zajmują łącznie
maksymalnie minutę kontekstu. Cała właściwa praca dotyczy `heap_4`.
Task 1 i Task 2 są krótkimi modelami prowadzonymi przez nauczyciela. Uczeń
najpierw zapisuje predykcję, ale nie buduje i nie debugguje tych programów
osobno podczas lekcji. Task 3, linkujący upstream `heap_4.c`, jest jedynym
centralnym przebiegiem ucznia.
## Stan wejściowy z K&R 5.4
Uczeń przychodzi z działającym kodem:
```text
allocbuf[64]
allocp
allocator_reset()
alloc_local(5) -> offset 0
alloc_local(7) -> offset 5
allocp -> offset 12
alloc_local(0) oraz OOM -> NULL bez przesunięcia kursora
```
Nie powtarzamy sposobu działania tego kodu. Zaczynamy od jego granicy:
> Jak zwolnić obszar 5 B spod offsetu 0, zachować późniejszy obszar 7 B spod
> offsetu 5 i ponownie wykorzystać powstałą dziurę?
W projekcie nie istnieje operacja zwolnienia pojedynczego obszaru. Nawet proste
cofnięcie kursora unieważniłoby wszystkie późniejsze przydziały. Brakuje
tożsamości bloku, jego rozmiaru, zbioru dziur i mechanizmu ich łączenia.
## Cel w jednym zdaniu
Po 30 minutach uczeń potrafi wyjaśnić i pokazać w debuggerze, jak `heap_4`
dodaje do liniowej areny nagłówki, wybór first-fit, podział bloku, listę wolnych
bloków oraz scalanie sąsiadów, a następnie odróżnić bieżącą ilość wolnej
pamięci, największy wolny blok i minimum-ever.
## Most K&R 5.4 -> `heap_4`
| K&R 5.4 — stan projektu | Ograniczenie | `heap_4` — nowy mechanizm |
| --- | --- | --- |
| `allocbuf[64]` i jeden `allocp` | istnieje tylko wolny ogon areny | `ucHeap[]` i bloki opisane nagłówkami |
| wynik to surowy adres | allocator nie zna później rozmiaru obszaru | `BlockLink_t` przed payloadem przechowuje rozmiar |
| kolejne przydziały tylko przesuwają kursor | nie można użyć dziury | lista wolnych bloków i wybór first-fit |
| brak zwalniania pojedynczego obszaru | reset odzyskuje wszystko naraz | `vPortFree()` odzyskuje wskazany blok |
| brak relacji między dziurami | sąsiednie dziury pozostają rozdzielone | porządek adresowy i coalescing w obie strony |
| tylko sukces albo `NULL` | brak informacji o fragmentacji | free, largest, liczba bloków i minimum-ever |
W realnym `heap_4.c` najwyższy bit `xBlockSize` oznacza blok przydzielony.
Uproszczone modele Task 12 pokazują geometrię i listę, ale nie odtwarzają tego
bitu. To jawna granica modelu.
## Przygotowanie przed lekcją
Budowanie, flashowanie i wykrywanie debug probe nie wchodzą do budżetu 30 minut.
Przed wejściem uczniów:
1. zbuduj Task 3 dla `pico2_w` / RP2350 RISC-V;
2. wgraj zweryfikowany obraz i otwórz sesję na tym samym ELF;
3. ustaw pierwszy breakpoint na `heap4_reset_checkpoint`;
4. przygotuj widok `ucHeap`, statystyk E01--E08,
`xFreeBytesRemaining` i `xMinimumEverFreeBytesRemaining`;
5. przygotuj Hazard3 jako awaryjny zamiennik — podczas lekcji użyj jednej
platformy, nigdy obu.
`heap_4` jest implementacją wybraną i linkowaną przez projekt. Nie jest
sprzętowym heapem ani peryferium RP2350.
## Przebieg 30 minut
| Czas | Tryb | Działanie i dowód |
| --- | --- | --- |
| 03 min | problem | Przypomnij wyłącznie offsety `0 -> 5 -> 12`. Uczeń próbuje zwolnić pierwszy obszar bez utraty drugiego i nazywa brakującą operację. |
| 34 min | kontekst | Jedno zdanie: FreeRTOS dostarcza wymienne implementacje tego samego API; ta karta i oficjalne przykłady Pico 2 W linkują `heap_4`. |
| 49 min | Task 1 — demo | Do żądania dodaj nagłówek, wyrównaj rozmiar, wybierz pierwszy pasujący blok i podziel go. Uczeń zapisuje `wanted` i `remainder` przed odsłonięciem wyniku. |
| 916 min | Task 2 — demo | Dla `A:48 | B:48 | C:48` pokaż listę `A -> C`, wstaw `B` po adresie i sprawdź scalenie w prawo oraz w lewo: `2 bloki / 96 B / largest 48 B -> 1 blok / 144 B / largest 144 B`. |
| 1627 min | Task 3 — RUN | Na przygotowanym celu przejdź przez osiem checkpointów prawdziwego `heap_4.c`: reset, trzy alokacje, free A, free C, free B i kontrolowany OOM. Uczeń wypełnia tabelę relacji. |
| 2730 min | wyjście | Uczeń wymienia trzy dodatki ponad K&R (`header`, `free list`, `coalescing`) i wyjaśnia: suma wolnych bajtów może być większa od największego bloku, a minimum-ever nie rośnie po `free`. |
## Task 1 — model nagłówka i splitu
Task 1 nie jest osobnym laboratorium. Na RV32 nagłówek modelu ma 8 B, więc
żądanie 13 B daje `wanted=24 B` i `remainder=232 B` z areny 256 B. Na AMD64
odpowiednie wartości to 16 B, 32 B i 224 B. Różnica ABI jest tylko pomocą w
zrozumieniu `sizeof` i wyrównania; podczas głównego przebiegu obowiązuje RP2350.
Predykcja ucznia:
```text
free list: [16 B] -> [80 B] -> [160 B]
request + header po wyrównaniu: 24 B
first-fit wybiera: __________
po split: allocated ______ B, remainder ______ B
```
Wykonywalny Task 1 dowodzi obecnie geometrii splitu jednego bloku. Samo
przeszukanie dwóch kandydatów jest elementem demonstracji i realnego
`pvPortMalloc()`, dlatego nie opisujemy Task 1 jako pełnego testu first-fit.
## Task 2 — model listy adresowej i coalescingu
```text
pamięć: [ A:48 FREE ][ B:48 USED ][ C:48 FREE ]
free list: A --------------------------------> C
vPortFree(B)
1. wstaw B pomiędzy A i C według adresu;
2. jeśli end(A) == addr(B), połącz A+B;
3. jeśli end(AB) == addr(C), połącz AB+C;
wynik: [ ABC:144 FREE ]
free list: A -> END
```
Uczeń przed odsłonięciem rysuje listę i wpisuje `2 -> 1`, `96 -> 144` oraz
`48 -> 144` odpowiednio dla liczby bloków, sumy i largest.
## Task 3 — centralny przebieg upstream `heap_4.c`
```text
RESET/INIT
-> heap4_reset_checkpoint # E01: 4080 B
-> ALLOC A(24)
-> heap4_alloc_a_checkpoint # E02: 4032 B
-> ALLOC B(40)
-> heap4_alloc_b_checkpoint # E03: 3968 B
-> ALLOC C(16)
-> heap4_alloc_c_checkpoint # E04: 3936 B
-> FREE A
-> heap4_free_a_checkpoint # E05: 3984 B, 2 bloki
-> FREE C
-> heap4_fragmented_checkpoint # E06: 4016 B, 2 bloki
-> FREE B
-> heap4_coalesced_checkpoint # E07: 4080 B, 1 blok
-> OOM request
-> task03_debug_checkpoint # E08: NULL, hook=1, pass=1
```
Na Hazard3 `sizeof(BlockLink_t)=8`, ale `portBYTE_ALIGNMENT=16`, dlatego
`xHeapStructSize=16`. Główny replay ma stałe, testowane wartości:
| Obserwacja | Oczekiwany wynik RV32/Hazard3 |
| --- | --- |
| E01--E04 | `4080 -> 4032 -> 3968 -> 3936`, zużycie `48/64/32` |
| E05--E06 | `3984 -> 4016`, nadal 2 bloki i `largest < total_free` |
| E07 | `final_free == initial_free == 4080`, `free_blocks == 1` |
| low-water mark | `minimum_ever == 3936` i nie rośnie po free |
| OOM | wynik `NULL`, hook `1`, brak uszkodzenia listy |
Minimalny dowód w GDB:
```gdb
p g_fragmented_stats
p g_final_stats
p g_a_consumed
p g_b_consumed
p g_c_consumed
p xFreeBytesRemaining
p xMinimumEverFreeBytesRemaining
p xStart
p pxEnd
x/96bx ucHeap
```
Szczegółowe mapowanie wszystkich diagramów do kodu, replay i dowodów zawiera
[`hazard3-iteration-plan.md`](hazard3-iteration-plan.md).
## Kryterium zaliczenia
Uczeń zalicza lekcję, jeżeli:
- wskaże, dlaczego jeden kursor nie wystarcza do zwolnienia obszaru ze środka;
- rozróżni nagłówek allocatora od payloadu i stosu `sp`;
- przewidzi przejście wolnej listy `2 -> 1` po zwolnieniu B;
- wyjaśni różnicę `total free` kontra `largest free block`;
- wyjaśni, dlaczego minimum-ever nie wraca po `free`;
- pokaże `aligned=1 oom=1 hooks=1 asserts=0 pass=1` w Task 3.
## Poza lekcją
Jako rozszerzenie lub pracę własną pozostają: pełne czytanie `heap_4.c`,
uruchomienie Task 12 na AMD64 i Hazard3, samodzielny deploy RP2350 oraz
porównanie z `heap_1`, `heap_2`, `heap_3` i `heap_5`.
+340
View File
@@ -0,0 +1,340 @@
% Generated from json/card_source.json by tools/render_card.py.
% Do not edit manually; update the JSON and regenerate.
\documentclass[12pt,a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[utf8]{inputenc}
\usepackage[polish]{babel}
\usepackage{csquotes}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{xcolor}
\usepackage[a4paper,left=2.15cm,right=2.15cm,top=0.5cm,bottom=1cm,headheight=24mm,headsep=3mm,includehead,includefoot]{geometry}
\usepackage{eso-pic}
\usepackage{marginnote}
\usepackage{array}
\usepackage{tabularx}
\usepackage{graphicx}
\usepackage{pdflscape}
\usepackage{float}
\usepackage{silence}
\WarningFilter{soulutf8}{This package is obsolete}
\usepackage{pdfcomment}
\usepackage{enumitem}
\usepackage{needspace}
\usepackage{fancyhdr}
\usepackage{lastpage}
\usepackage{microtype}
\usepackage[most]{tcolorbox}
\usepackage{qrcode}
\IfFileExists{references.bib}{%
\usepackage[backend=biber,style=numeric,sorting=none]{biblatex}%
\addbibresource{references.bib}%
}{}
\hypersetup{hidelinks}
\IfFileExists{build-meta.tex}{%
\input{build-meta.tex}%
}{%
\newcommand{\BuildCommit}{lokalna}%
}
\newcommand{\DocumentAuthor}{Mateusz Pabiszczak}
\newcommand{\DocumentYear}{2026}
\newcommand{\CardSeries}{freertos-c}
\newcommand{\CardSeriesTitle}{FreeRTOS C}
\newcommand{\CardNumber}{01}
\newcommand{\CardCount}{15}
\newcommand{\CardSlug}{heap4-mechanics}
\newcommand{\CardVersion}{v00.01}
\newcommand{\CardStatus}{Gotowa}
\newcommand{\DocumentUUID}{87e48095-7793-54e3-9230-4e61d31bd459}
\newcommand{\EmptyCheck}{\(\square\)}
\newcommand{\EscAnswerLines}[1][3]{\par\noindent\dotfill\par\noindent\dotfill\par\noindent\dotfill}
\newcommand{\EscWriteRow}[1][2.8em]{\rule{0pt}{#1}}
\setlength{\parindent}{0pt}
\setlength{\parskip}{0.45em}
\setlength{\headheight}{24mm}
\setlength{\headsep}{3mm}
\setlength{\footskip}{8mm}
\setlength{\marginparwidth}{1.5cm}
\setlength{\marginparsep}{0.25cm}
\renewcommand{\arraystretch}{1.22}
% Margin separator lines disabled for layout trial.
\newcommand{\ESCPageResourceHeader}{%
\begingroup%
\setlength{\parskip}{0pt}%
\setlength{\fboxsep}{0pt}%
\setlength{\fboxrule}{0.35pt}%
\setlength{\arrayrulewidth}{0.35pt}%
\noindent\fbox{%
\begin{minipage}[c][23.5mm][c]{\dimexpr\textwidth-2\fboxrule\relax}%
\begin{minipage}[c][23mm][c]{24mm}\centering%
\href{https://zsl-gitea.mpabi.pl/edu-freertos-c/lab-rv32i-freertos-heap4}{\qrcode[level=L,height=22mm]{https://zsl-gitea.mpabi.pl/edu-freertos-c/lab-rv32i-freertos-heap4}}%
\end{minipage}%
\vrule width0.35pt%
\begin{minipage}[c][23mm][c]{\dimexpr\linewidth-48mm-0.7pt\relax}%
\setlength{\tabcolsep}{0pt}%
\renewcommand{\arraystretch}{0}%
\begin{tabularx}{\linewidth}{@{}p{\dimexpr0.50000000\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.15126050\linewidth-\arrayrulewidth\relax}|X@{}}%
\parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries TITLE}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{7.7}{7.9}\selectfont\ttfamily\bfseries heap\_4: From Linear Cursor to Reusable Blocks}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries VER.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.6}{6.8}\selectfont\ttfamily\bfseries v00.01}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries DATETIME}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries 2026-07-20T00:00:00+02:00}\hspace{0.45mm}}\par} \\%
\end{tabularx}%
\par\nointerlineskip%
\hrule height0.35pt%
\nointerlineskip%
\begin{tabularx}{\linewidth}{@{}p{\dimexpr0.50000000\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.25210084\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.14285714\linewidth-\arrayrulewidth\relax}|X@{}}%
\parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries PROJECT}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{7.7}{7.9}\selectfont\ttfamily\bfseries Freestanding C nad FreeRTOS}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries SERIES}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries FreeRTOS C}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries CARD}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries 01/15}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries SHEET}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries \thepage/\pageref{LastPage}}\hspace{0.45mm}}\par} \\%
\end{tabularx}%
\par\nointerlineskip%
\hrule height0.35pt%
\nointerlineskip%
\begin{tabularx}{\linewidth}{@{}p{\dimexpr0.05882353\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.05042017\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.14705882\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.09243697\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.07563025\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.07563025\linewidth-\arrayrulewidth\relax}|X@{}}%
\parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries SUBJ.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries Inf.}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries PROG.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries CORE}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries SCOPE}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries LEVEL}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries POS.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hbox to\linewidth{\hspace{0.35mm}{\fontsize{3.4}{3.75}\selectfont\ttfamily\bfseries GITEA UUID}\hfill{\fontsize{3.4}{3.75}\selectfont\ttfamily 87e48095-7793-54e3-9230-4e61d31bd459}\hspace{0.35mm}}\par\nointerlineskip\hbox to\linewidth{\hspace{0.35mm}{\fontsize{3.4}{3.75}\selectfont\ttfamily\bfseries CARD UUID} {\fontsize{3.4}{3.75}\selectfont\ttfamily aea09ab7-9bfd-5368-8d1b-c8724871c1a7}\hfill{\fontsize{3.4}{3.75}\selectfont\ttfamily\bfseries AUTHOR Mateusz Pabiszczak}\hspace{0.35mm}}} \\%
\end{tabularx}%
\par\nointerlineskip%
\hrule height0.35pt%
\nointerlineskip%
\begin{tabularx}{\linewidth}{@{}X@{}}%
\parbox[c][3.46276mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{4.5}{4.85}\selectfont\ttfamily\bfseries GITEA} {\fontsize{4.5}{4.85}\selectfont\ttfamily\href{https://zsl-gitea.mpabi.pl/edu-freertos-c/lab-rv32i-freertos-heap4}{\nolinkurl{https://zsl-gitea.mpabi.pl/edu-freertos-c/lab-rv32i-freertos-heap4}}}} \\%
\end{tabularx}%
\par\nointerlineskip%
\hrule height0.35pt%
\nointerlineskip%
\begin{tabularx}{\linewidth}{@{}X@{}}%
\parbox[c][3.46276mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{4.5}{4.85}\selectfont\ttfamily\bfseries HTML} {\fontsize{4.5}{4.85}\selectfont\ttfamily\href{https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/aea09ab7-9bfd-5368-8d1b-c8724871c1a7}{\nolinkurl{https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/aea09ab7-9bfd-5368-8d1b-c8724871c1a7}}}} \\%
\end{tabularx}%
\end{minipage}%
\vrule width0.35pt%
\begin{minipage}[c][23mm][c]{24mm}\centering%
\href{https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/aea09ab7-9bfd-5368-8d1b-c8724871c1a7}{\qrcode[level=L,height=22mm]{https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/aea09ab7-9bfd-5368-8d1b-c8724871c1a7}}%
\end{minipage}%
\end{minipage}%
}%
\endgroup%
}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[C]{\ESCPageResourceHeader}
\fancyfoot[L]{{\fontsize{6.4}{7.0}\selectfont\ttfamily UUID \DocumentUUID}}
\fancyfoot[C]{{\fontsize{6.4}{7.0}\selectfont\ttfamily \DocumentAuthor}}
\fancyfoot[R]{{\fontsize{6.4}{7.0}\selectfont\ttfamily STRONA \thepage/\pageref{LastPage}}}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0.35pt}
\newcommand{\ESCMarginTag}[4]{%
\hspace*{#1}\textcolor{#2}{#3~#4}%
}
\newcommand{\ESCSectionBlockStart}{%
\Needspace{8\baselineskip}%
\par\vspace{0.35em}%
\noindent\textcolor{black!28}{\rule{\textwidth}{0.35pt}}%
\par\vspace{0.15em}%
}
\newcommand{\ESCSectionBlockEnd}{%
\par\vspace{0.45em}%
}
\newcommand{\ESCTinyStepSeparator}{%
\par\vspace{0.10em}%
\noindent{\color{black!18}\leaders\hbox{\rule{0.60em}{0.22pt}\hspace{0.38em}}\hfill\kern0pt}%
\par\vspace{0.10em}%
}
\newtcolorbox{ESCTaskFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black,boxsep=0pt,left=1.4mm,right=1.4mm,top=0.8mm,bottom=0.8mm,colbacktitle=black!7,coltitle=black,title={\ttfamily\bfseries TASK\quad #1}}
\newtcolorbox{ESCBlockFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black!55,boxsep=0pt,left=1.0mm,right=1.0mm,top=0.6mm,bottom=0.6mm,colbacktitle=black!4,coltitle=black,title={\ttfamily\bfseries BLOCK\quad #1}}
\newtcolorbox{ESCStepFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black!28,boxsep=0pt,left=0.8mm,right=0.8mm,top=0.45mm,bottom=0.45mm,colbacktitle=black!2,coltitle=black,title={\ttfamily STEP\quad #1}}
\newtcolorbox{ESCConclusionFrame}{enhanced,breakable,arc=0pt,boxrule=0.45pt,colback=blue!2,colframe=blue!45!black,boxsep=0pt,left=1.2mm,right=1.2mm,top=0.7mm,bottom=0.7mm,colbacktitle=blue!7,coltitle=black,title={\ttfamily\bfseries WNIOSEK}}
\newcommand{\ESCLearningTreeWidget}{%
\reversemarginpar
\marginnote[%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\begin{minipage}[t]{\marginparwidth}%
\raggedright
{\bfseries\textcolor{black!65}{TECH}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\hspace*{0.28em}\textcolor{orange!85!black}{EK~LOCAL~RTOS}\textcolor{black!48}{\pdftooltip[width=\textwidth]{.01}{Uczeń odtwarza osiem checkpointów Hazard3 i odróżnia current free, largest free block oraz\textCR minimum-ever.}}\par%
\hspace*{0.54em}\textcolor{blue!70!black}{KW~LOCAL~RTOS}\textcolor{black!48}{\pdftooltip[width=\textwidth]{.01}{Łączy kod, diagram i zweryfikowany dowód.}}\par%
\end{minipage}%
}%
\end{minipage}%
]{}
\normalmarginpar
\marginnote{%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\hspace*{0.06cm}%
\begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}%
\raggedright
{\bfseries\textcolor{black!65}{OG}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\hspace*{0.28em}\textcolor{green!50!black}{EN~LOCAL~EN~RTOS}\textcolor{black!48}{\pdftooltip[width=\textwidth]{.01}{Uczeń wyjaśnia nagłówek, wyrównanie, first-fit, split, adresową listę wolnych bloków i\textCR dwustronne coalescing.}}\par%
\hspace*{0.54em}\textcolor{blue!70!black}{KW~LOCAL~KW~RTOS}\textcolor{black!48}{\pdftooltip[width=\textwidth]{.01}{Łączy kod, diagram i zweryfikowany dowód.}}\par%
\end{minipage}%
}%
\end{minipage}%
}
}
\begin{document}
\sloppy
\vspace{1.0em}
\noindent{\Large\bfseries Cel karty}\par
\vspace{0.35em}
Uczeń przechodzi od liniowego kursora z K\&R 5.4 do wielokrotnego użycia tej samej areny: rozpoznaje nagłówek bloku, first-fit, split oraz dwustronne scalanie wolnych bloków w heap\_4.
\vspace{0.8em}
\noindent\textcolor{black!25}{\rule{\textwidth}{0.35pt}}
\vspace{0.7em}
\noindent{\Large\bfseries Zakres karty}\par
\vspace{0.35em}
Dwa małe modele C przygotowują przewidywanie. Główny replay uruchamia prawdziwe FreeRTOS-Kernel V11.3.0 heap\_4 na RV32I/Hazard3 i dowodzi fragmentacji, pełnego scalenia, minimum-ever oraz kontrolowanego OOM.
\vspace{0.55em}
\small\begin{tabularx}{\textwidth}{@{}>{\ttfamily\raggedright\arraybackslash}p{1.10cm}>{\ttfamily\raggedright\arraybackslash}p{1.45cm}X>{\raggedright\arraybackslash}p{2.50cm}>{\raggedright\arraybackslash}p{1.75cm}>{\ttfamily\raggedright\arraybackslash}p{1.50cm}@{}}
\textbf{Lekcja} & \textbf{Task} & \textbf{Najważniejsza idea} & \textbf{Priorytet} & \textbf{Status} & \textbf{Version} \\ \hline
\hline
\textbf{\texttt{FC01}} & \textbf{\texttt{Task01}} & \textbf{headers, address-ordered free list, first-fit, split, coalescing and heap statistics} & \textbf{główny} & \textbf{opracowane} & \textbf{\texttt{v00.01}} \\ \hline
\end{tabularx}
\normalsize
\vspace{0.8em}
\noindent\textcolor{black!25}{\rule{\textwidth}{0.35pt}}
\clearpage
\ESCSectionBlockStart
\noindent{\small\bfseries A1 — Od liniowego kursora do odzyskiwalnej areny \hfill część 1/1 \textperiodcentered\ r1 c1}\par
\vspace{0.35em}
\begin{figure}[H]
\centering
\includegraphics[width=0.47121\linewidth,trim=0.000bp 0.000bp 0.000bp 0.000bp,clip]{\detokenize{../assets/a1-context.png}}
\caption{A1 CONTEXT — od żądania aplikacji do rzeczywistych bajtów ucHeap. — część 1/1 (r1 c1).}
\label{fig:a1-context}
\end{figure}
\Needspace{6\baselineskip}
\begin{tcolorbox}[enhanced,arc=0pt,boxrule=0.35pt,colback=blue!2,colframe=blue!45!black,boxsep=0pt,left=1.2mm,right=1.2mm,top=0.7mm,bottom=0.7mm,colbacktitle=blue!7,coltitle=black,title={\ttfamily\bfseries INTERPRETACJA}]
Aplikacja korzysta z publicznego API, heap\_4 zarządza jedną areną, a Hazard3 pokazuje jej rzeczywisty stan.
\end{tcolorbox}
\ESCSectionBlockEnd
\clearpage
\ESCSectionBlockStart
\noindent{\small\bfseries A2 — Nagłówek, payload i lista wolnych bloków \hfill część 1/1 \textperiodcentered\ r1 c1}\par
\vspace{0.35em}
\begin{figure}[H]
\centering
\includegraphics[width=0.47273\linewidth,trim=0.000bp 0.000bp 0.000bp 0.000bp,clip]{\detokenize{../assets/a2-structure.png}}
\caption{A2 STRUCTURE — metadane wewnątrz areny i adresowo uporządkowana lista. — część 1/1 (r1 c1).}
\label{fig:a2-structure}
\end{figure}
\Needspace{6\baselineskip}
\begin{tcolorbox}[enhanced,arc=0pt,boxrule=0.35pt,colback=blue!2,colframe=blue!45!black,boxsep=0pt,left=1.2mm,right=1.2mm,top=0.7mm,bottom=0.7mm,colbacktitle=blue!7,coltitle=black,title={\ttfamily\bfseries INTERPRETACJA}]
Każdy blok ma nagłówek, a tylko wolne bloki wykorzystują wskaźnik next do budowy listy uporządkowanej adresami.
\end{tcolorbox}
\ESCSectionBlockEnd
\clearpage
\begin{landscape}
\ESCSectionBlockStart
\noindent{\small\bfseries A5 — First-fit, fragmentacja i pełne scalenie \hfill część 1/1 \textperiodcentered\ r1 c1}\par
\vspace{0.35em}
\begin{figure}[H]
\centering
\includegraphics[width=0.86383\linewidth,trim=0.000bp 0.000bp 0.000bp 0.000bp,clip]{\detokenize{../assets/a5-flow.png}}
\caption{A5 FLOW — inicjalizacja, A/B/C, fragmentacja, dwustronne scalenie i OOM. — część 1/1 (r1 c1).}
\label{fig:a5-flow}
\end{figure}
\Needspace{6\baselineskip}
\begin{tcolorbox}[enhanced,arc=0pt,boxrule=0.35pt,colback=blue!2,colframe=blue!45!black,boxsep=0pt,left=1.2mm,right=1.2mm,top=0.7mm,bottom=0.7mm,colbacktitle=blue!7,coltitle=black,title={\ttfamily\bfseries INTERPRETACJA}]
Jeden przebieg A/B/C przechodzi od pustej areny przez trzy przydziały i dwa wolne obszary do ponownie scalonego bloku.
\end{tcolorbox}
\ESCSectionBlockEnd
\end{landscape}
\clearpage
\ESCSectionBlockStart
\noindent{\small\bfseries A6 — Stan bloku a stan statystyk \hfill część 1/1 \textperiodcentered\ r1 c1}\par
\vspace{0.35em}
\begin{figure}[H]
\centering
\includegraphics[width=0.48636\linewidth,trim=0.000bp 0.000bp 0.000bp 0.000bp,clip]{\detokenize{../assets/a6-state.png}}
\caption{A6 STATE — wolny, przydzielony, rozdzielony, scalony oraz minimum-ever. — część 1/1 (r1 c1).}
\label{fig:a6-state}
\end{figure}
\Needspace{6\baselineskip}
\begin{tcolorbox}[enhanced,arc=0pt,boxrule=0.35pt,colback=blue!2,colframe=blue!45!black,boxsep=0pt,left=1.2mm,right=1.2mm,top=0.7mm,bottom=0.7mm,colbacktitle=blue!7,coltitle=black,title={\ttfamily\bfseries INTERPRETACJA}]
Bieżąca liczba wolnych bajtów może rosnąć po free, ale minimum-ever pozostaje historycznym minimum.
\end{tcolorbox}
\ESCSectionBlockEnd
\clearpage
\begin{landscape}
\ESCSectionBlockStart
\noindent{\small\bfseries A7 — Dowód w Hazard3 i ELF \hfill część 1/1 \textperiodcentered\ r1 c1}\par
\vspace{0.35em}
\begin{figure}[H]
\centering
\includegraphics[width=0.86596\linewidth,trim=0.000bp 0.000bp 0.000bp 0.000bp,clip]{\detokenize{../assets/a7-runtime.png}}
\caption{A7 RUNTIME — kod, ELF, checkpoint, pamięć i statystyki jednego przebiegu. — część 1/1 (r1 c1).}
\label{fig:a7-runtime}
\end{figure}
\Needspace{6\baselineskip}
\begin{tcolorbox}[enhanced,arc=0pt,boxrule=0.35pt,colback=blue!2,colframe=blue!45!black,boxsep=0pt,left=1.2mm,right=1.2mm,top=0.7mm,bottom=0.7mm,colbacktitle=blue!7,coltitle=black,title={\ttfamily\bfseries INTERPRETACJA}]
Ten sam artefakt łączy źródło, symbole ELF, osiem checkpointów, pamięć ucHeap i końcowe asercje.
\end{tcolorbox}
\ESCSectionBlockEnd
\end{landscape}
\clearpage
\ESCSectionBlockStart
\section{Task01 — mechanika heap\_4}
\begin{ESCTaskFrame}{TASK01 · Predict and prove reusable heap\_4 blocks}
{\scriptsize\ttfamily aea09ab7-9bfd-5368-8d1b-c8724871c1a7}\par
\begin{ESCBlockFrame}{A — przewidywanie}
Przejdź przez model nagłówka, split i porządek adresowy przed uruchomieniem prawdziwego heap\_4.
\begin{ESCStepFrame}{size · Policz rozmiary całych bloków.}
Rozróżnij surowy BlockLink\_t (8 bajtów) od xHeapStructSize (16 bajtów), dodaj nagłówek do żądań 24, 40 i 16 bajtów, a następnie wyrównaj wynik do 16.
\end{ESCStepFrame}
\begin{ESCStepFrame}{list · Narysuj listę po free(A,C).}
Zaznacz, dlaczego przydzielony B rozdziela dwa wolne obszary.
\end{ESCStepFrame}
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{B — replay Hazard3}
Powiąż każdy stan diagramu z deterministycznym checkpointem E01--E08.
\begin{ESCStepFrame}{allocate · Sprawdź E01--E04.}
Potwierdź wartości 4080, 4032, 3968 i 3936 oraz zużycie 48/64/32 bajtów.
\end{ESCStepFrame}
\begin{ESCStepFrame}{release · Sprawdź E05--E07.}
Porównaj liczbę wolnych bloków i udowodnij scalenie poprzedniego, a potem następnego sąsiada.
\end{ESCStepFrame}
\begin{ESCStepFrame}{verify · Sprawdź E08.}
Udowodnij zachowanie minimum-ever, kontrolowany OOM, brak asercji i PASS=1.
\end{ESCStepFrame}
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{Ćwiczenie · Ćwiczenie — ta sama suma, inna użyteczność}
Zaproponuj dwa układy wolnych bloków o tej samej sumie bajtów, ale tylko jeden zdolny obsłużyć wskazane duże żądanie. Uzasadnij odpowiedź przez largest-free-block.
\par\textbf{Evidence:} Dwa diagramy listy, suma wolnych bajtów, rozmiar największego bloku i wynik pvPortMalloc.
\par\textbf{Acceptance:} Uczeń nie utożsamia sumy wolnej pamięci z możliwością pojedynczego dużego przydziału.
\end{ESCBlockFrame}
\tcblower\textbf{Task acceptance:} Na RV32 BlockLink\_t ma 8 bajtów, lecz 16-bajtowe wyrównanie portu daje xHeapStructSize=16; A/B/C zużywają 48/64/32 bajty; po free(A,C) są dwa wolne bloki, po free(B) jeden; final free = initial free; minimum-ever nie rośnie; OOM zwraca NULL; PASS=1.
\end{ESCTaskFrame}
\begin{ESCConclusionFrame}
heap\_4 odzyskuje pamięć dzięki metadanym w arenie, adresowo uporządkowanej liście i scalaniu sąsiadów. Suma wolnych bajtów nie zastępuje informacji o największym bloku, a minimum-ever jest historią, nie bieżącym stanem.
\end{ESCConclusionFrame}
\ESCSectionBlockEnd
\end{document}
+102
View File
@@ -0,0 +1,102 @@
# FC01 — plan iteracji diagramów w Hazard3
## Zasada
Kursor porusza się po diagramie, nie po przypadkowych liniach programu.
Każda kotwica ma jeden z dwóch kontraktów:
- `CODE` — otwiera właściwe źródło lub dowód ELF; nie uruchamia programu;
- `RUN` — odtwarza program od czystego resetu do wskazanego checkpointu,
ustawia Neovima i udostępnia zestaw wyrażeń GDB.
Ten sam snapshot może wystąpić w kilku perspektywach. Nie powtarza to
eksperymentu: A2 pyta o strukturę, A6 o stan, a A7 o jakość dowodu.
## A1 CONTEXT — granice odpowiedzialności
| Step | Tryb | Otwórz | Pytanie kontrolne |
|---|---|---|---|
| 01 Application | CODE | `main()` | Czy aplikacja dotyka metadanych allocatora? |
| 02 FreeRTOS API | CODE | wywołania `pvPortMalloc`, `vPortFree`, stats | Gdzie kończy się publiczny kontrakt? |
| 03 heap_4 | CODE | upstream `heap_4.c` | Które funkcje wybierają, dzielą i scalają blok? |
| 04 ucHeap | CODE | definicja `ucHeap[4096]` | Gdzie fizycznie leżą nagłówki i payload? |
| 05 Hazard3 | CODE | test symboli ELF | Czy debugujemy dokładnie artefakt z karty? |
A1 nie ma checkpointu: opisuje architekturę, a nie zdarzenie runtime.
## A2 STRUCTURE — nagłówek, split i scalenie
| Step | Tryb / replay | Obserwuj | Warunek zaliczenia |
|---|---|---|---|
| 01 Header | CODE | `BlockLink_t`, `portBYTE_ALIGNMENT` | raw header=8 B, alignment=16 B, `xHeapStructSize`=16 B |
| 02 Alignment | RUN E02 | `g_a`, `g_a_consumed` | A jest wyrównane, cały blok zużywa 48 B |
| 03 Split | RUN E02 | `g_after_a_stats` | pozostaje 1 wolny blok i 4032 B |
| 04 Address order | RUN E06 | `g_a`, `g_b`, `g_c`, stats | adresy rosną, a B rozdziela 2 wolne obszary |
| 05 Merge previous | RUN E06 + step | `pxIterator`, `pxBlockToInsert` | poprzedni/lewy blok jest testowany i scalany pierwszy |
| 06 Merge next | RUN E06 + step | powiększony blok i `pxNextFreeBlock` | następny/prawy blok jest scalany drugi |
Dla kroków 0506 po replay E06:
```gdb
tbreak prvInsertBlockIntoFreeList
continue # wykonaj vPortFree(g_b)
p pxIterator
p pxBlockToInsert
next # test poprzedniego/lewego sąsiada
next # test następnego/prawego sąsiada
```
## A5 FLOW — pełny przebieg
| Event | Operacja | Wartości RV32/Hazard3 | Dowód |
|---|---|---|---|
| E01 | reset/init | free=4080 | 1 blok, largest=4080 |
| E02 | alloc A(24) | consumed=48, free=4032 | A != NULL, aligned=1 |
| E03 | alloc B(40) | consumed=64, free=3968 | B != NULL, aligned=1 |
| E04 | alloc C(16) | consumed=32, free=3936 | C != NULL, aligned=1 |
| E05 | free A | free=3984 | 2 wolne bloki |
| E06 | free C | free=4016 | 2 bloki, free > largest |
| E07 | free B | free=largest=4080 | 1 wolny blok, minimum-ever=3936 |
| E08 | oversized alloc | NULL | hook=1, asserts=0, layout=1, PASS=1 |
Każdy Enter na A5 wykonuje czysty replay do dokładnie jednego eventu. Nie
kontynuujemy stanu pozostałego po poprzednim kroku.
## A6 STATE — znaczenie liczników
| Step | Replay | Hipoteza |
|---|---|---|
| 01 Free | E01 | current free i largest są równe |
| 02 Allocated | E04 | przydziały zmniejszają current free do 3936 |
| 03 Fragmented | E06 | total free może być większe od largest |
| 04 Coalesced | E07 | current, largest i initial znów są równe |
| 05 Minimum-ever | E08 | minimum-ever pozostaje 3936 mimo odzyskania areny |
## A7 RUNTIME — jakość dowodu
| Step | Tryb / replay | Co musi znaleźć się w dowodzie |
|---|---|---|
| 01 Source | CODE | blob źródła eksperymentu |
| 02 ELF | CODE | SHA artefaktu i symbole upstream/checkpointów |
| 03 Fragmented | RUN E06 | PC, 2 bloki, free=4016, largest<free |
| 04 Memory | RUN E06 | adres `ucHeap`, adresy A/B/C i bajty nagłówków |
| 05 Verdict | RUN E08 | final=initial, OOM NULL, hook=1, asserts=0, PASS=1 |
Snapshot do raportu jest kompletny dopiero wtedy, gdy łączy nazwę eventu,
tożsamość ELF, pozycję programu i wartości rozstrzygające hipotezę.
## Świadomie pominięte perspektywy
- A3 DISPATCH — allocator nie ma callbacku ani mechanizmu dispatch;
- A4 APPLICATION — konkretny eksperyment A/B/C jest pełnym A5;
- A8 PATTERNS — first-fit i intrusive address-ordered list są już mierzalne w
A2 i A5; osobny diagram powielałby materiał.
## Definition of done
1. Hostowe modele i adapter przechodzą `tests/test_host.sh`.
2. ELF RV32 zawiera upstream `heap_4` oraz osiem symboli checkpointów.
3. Hazard3 RTL kończy Task03 kodem `0`.
4. E01E08 spełniają wyrażenia z `debug_checkpoints`.
5. Wszystkie kroki RUN mają `snapshot_ref`; kroki CODE nie udają runtime.
6. SVG, HTML i TeX powstają z tego samego `json/card_source.json`.
+280 -224
View File
@@ -1,21 +1,19 @@
\documentclass[12pt]{article}
\documentclass[10pt]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[polish]{babel}
\usepackage[a4paper,margin=2.0cm]{geometry}
\usepackage{array}
\usepackage{tabularx}
\usepackage{xcolor}
\usepackage{listings}
\usepackage{hyperref}
\usepackage{fancyhdr}
\usepackage{lastpage}
\usepackage[a4paper,margin=1.55cm]{geometry}
\usepackage{array,tabularx,booktabs}
\usepackage{amsmath,amssymb}
\usepackage{xcolor,listings}
\usepackage{hyperref,fancyhdr,lastpage}
\usepackage{enumitem}
\definecolor{accent}{HTML}{16324A}
\definecolor{accentlight}{HTML}{EEF3F7}
\definecolor{rulegray}{HTML}{D7DEE5}
\hypersetup{colorlinks=true,linkcolor=blue,urlcolor=blue,citecolor=blue}
\definecolor{success}{HTML}{236B45}
\hypersetup{colorlinks=true,linkcolor=accent,urlcolor=blue}
\IfFileExists{build-meta.tex}{\input{build-meta.tex}}{\newcommand{\BuildCommit}{local}}
\newcommand{\PublisherDomain}{mpabi}
\newcommand{\DocumentAuthor}{M. Pabiszczak}
@@ -23,236 +21,294 @@
\newcommand{\CardArea}{inf}
\newcommand{\CardSeries}{rv32i-freertos}
\newcommand{\CardNumber}{01}
\newcommand{\CardCount}{??}
\newcommand{\CardCount}{15}
\newcommand{\CardSlug}{heap4}
\newcommand{\CardVersion}{v0.1}
\newcommand{\CardVersion}{v0.3}
\newcommand{\DocumentKey}{\PublisherDomain/\CardArea/\CardSeries/\CardNumber/\CardSlug/\CardVersion}
\newcommand{\DocumentUUID}{4654524c-73f2-46df-83a3-d448ba6d6c5a}
\renewcommand{\lstlistingname}{Listing}
\renewcommand{\lstlistlistingname}{Lista listingów}
\lstset{basicstyle=\ttfamily\small,columns=fullflexible,keepspaces=true,frame=single,breaklines=true,showstringspaces=false,numbers=left,numberstyle=\tiny\color{accent},stepnumber=1,numbersep=8pt,backgroundcolor=\color{accentlight},rulecolor=\color{rulegray},captionpos=b}
\newcommand{\TaskSection}[5]{\section{Task #1: #3}\subsection*{Algorytm i zakresy linii}#4\lstinputlisting[language=C,caption={Task #1: #3},label={lst:task#1-c}]{../src/tasks/#2.c}\subsection*{Co sprawdzić w ASM}#5\lstinputlisting[basicstyle=\ttfamily\scriptsize,caption={ASM dla task #1},label={lst:task#1-asm}]{../build/#2/#2.s}}
\lstset{
language=C,basicstyle=\ttfamily\scriptsize,columns=fullflexible,
keepspaces=true,frame=single,breaklines=true,showstringspaces=false,
numbers=none,backgroundcolor=\color{accentlight},rulecolor=\color{rulegray}
}
\pagestyle{fancy}
\fancyhf{}
\lhead{\textbf{FreeRTOS: heap\_4}}
\rhead{\small \DocumentAuthor, \DocumentYear}
\lhead{\textbf{FC01 · FreeRTOS \texttt{heap\_4}}}
\rhead{\small 30 minut · Pico 2 W / RP2350}
\lfoot{\scriptsize commit \BuildCommit}
\cfoot{\scriptsize \thepage/\pageref{LastPage}}
\rfoot{\scriptsize uuid \DocumentUUID}
\renewcommand{\headrulewidth}{0.4pt}
\setlength{\headheight}{18pt}
\setlength{\footskip}{24pt}
\rfoot{\scriptsize V11.3.0 / \CardVersion}
\setlength{\headheight}{14pt}
\setlength{\footskip}{19pt}
\setlist[itemize]{nosep,leftmargin=1.45em}
\setlist[enumerate]{nosep,leftmargin=1.65em}
\newcommand{\checkline}{\(\square\)\;}
\newcommand{\blank}[1]{\rule{#1}{.2pt}}
\begin{document}
\sloppy
\noindent
\begin{tabular}{@{}lp{0.72\textwidth}@{}}
Project: & FreeRTOS: \texttt{heap\_4} \\
Subject: & Informatyka \\
Level: & 1, Zakres Podstawowy, PP \\
Topic: & dynamiczna alokacja pamięci, lista wolnych bloków, scalanie bloków \\
Revision date: & 02.05.2026 \\
Status: & Draft \\
Card in series: & \small\texttt{\CardNumber/\CardCount} \\
Card version: & \small\texttt{\CardVersion} \\
Card key: & \small\texttt{\DocumentKey} \\
UUID: & \small\texttt{\DocumentUUID} \\
Commit: & \small\texttt{\BuildCommit} \\
Scope: & pierwsza karta serii FreeRTOS; model dydaktyczny mechaniki \texttt{heap\_4.c} \\
Files used: & \small
\begin{tabular}[t]{@{}l@{}}
Task01: \texttt{task01\_heap\_map.c} \\
Task02: \texttt{task02\_free\_list.c} \\
Task03: \texttt{task03\_heap\_init.c} \\
Task04: \texttt{task04\_alignment.c} \\
Task05: \texttt{task05\_malloc\_first\_fit.c} \\
Task06: \texttt{task06\_split\_block.c} \\
Task07: \texttt{task07\_free\_insert.c} \\
Task08: \texttt{task08\_coalescing.c} \\
Task09: \texttt{task09\_config\_macros.c} \\
Task10: \texttt{task10\_heap4\_demo.c} \\
Common: \texttt{heap4\_common.h} \\
Runtime: \texttt{memops.c} \\
Startup: \texttt{crt0.S}
\end{tabular}
\end{tabular}
\vspace{0.8em}
\noindent\rule{\textwidth}{0.5pt}
\begin{center}
{\LARGE\bfseries FreeRTOS \texttt{heap\_4}: od kursora do wolnych bloków}\par
\vspace{.25em}
{\large bezpośrednia kontynuacja projektu K\&R 5.4}\par
\end{center}
\section{Cel}
Ta karta zaczyna serię FreeRTOS od \texttt{heap\_4}, ponieważ ten plik jest
naturalnym pomostem po kartach z języka C. Wymaga wskaźników, tablic,
struktur, listy jednokierunkowej, \texttt{static}, \texttt{typedef}, makr oraz
arytmetyki adresów, ale nie wymaga jeszcze schedulera, przerwań ani
uruchamiania tasków.
Przykłady są małym modelem edukacyjnym mechaniki \texttt{heap\_4.c}. Nie są
kopią pliku upstream FreeRTOS. Na RV32I wyniki są zapisywane w globalnych
zmiennych \texttt{volatile}; \texttt{printf} jest dostępny wyłącznie w wariancie
hostowym przez flagę \texttt{-DHOST\_PRINTF=1}.
Listingi C i ASM mają numerowane linie. Opisy w sekcjach tasków odnoszą się
do numerów widocznych po lewej stronie listingów C.
\lstinputlisting[language=C,caption={Wspólny nagłówek dla karty \texttt{heap\_4}},label={lst:common-h}]{../src/tasks/heap4_common.h}
\subsection{\texttt{static inline} w nagłówku}
Helpery z Listingu~\ref{lst:common-h} są zapisane jako \texttt{static inline},
bo każdy task jest osobnym programem i dołącza ten sam nagłówek. \texttt{static}
daje prywatną kopię funkcji w każdym pliku \texttt{.c}, a \texttt{inline}
pozwala kompilatorowi wstawić krótkie obliczenia, takie jak wyrównanie rozmiaru
lub sprawdzenie bitu alokacji, bez kosztu zwykłego wywołania funkcji.
\section{Mapa karty}
\begin{tabularx}{\textwidth}{|p{2.1cm}|X|}
\hline
Task & Temat \\
\hline
1 & Mapa pojęć: obszar sterty, nagłówek bloku i bit alokacji. \\
\hline
2 & Lista wolnych bloków jako lista jednokierunkowa. \\
\hline
3 & Inicjalizacja sterty, pierwszy wolny blok i wartownik końca listy. \\
\hline
4 & Wyrównanie rozmiarów żądań i adresu początku sterty. \\
\hline
5 & Pierwszy pasujący blok, czyli zasada \texttt{first fit}. \\
\hline
6 & Podział dużego bloku na część przydzieloną i pozostały blok wolny. \\
\hline
7 & Zwracanie bloku i wstawianie go do listy według adresu. \\
\hline
8 & Scalanie sąsiednich wolnych bloków. \\
\hline
9 & Makra konfiguracyjne oraz hostowy \texttt{TRACE\_PRINTF}. \\
\hline
10 & Mini-demonstracja sekwencji \texttt{malloc/free}. \\
\hline
\noindent\begin{tabularx}{\textwidth}{@{}p{1.45cm}Xp{1.4cm}X@{}}
\toprule
Karta & FC01 / \CardCount & Czas & 30 minut \\
Płytka & Raspberry Pi Pico 2 W & Układ & RP2350, rdzeń RISC-V \\
Allocator & FreeRTOS-Kernel V11.3.0 & Źródło & upstream \texttt{heap\_4.c} \\
Wersja & \CardVersion & Data & 19.07.2026 \\
\bottomrule
\end{tabularx}
\TaskSection{01}{task01_heap_map}{mapa \texttt{heap\_4}}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--7 definiują zmienne \texttt{volatile}, czyli obserwowalne wyniki taska.
\item Linie 11--16 tworzą pojedynczy blok i zapisują w jego polu \texttt{size} rozmiar z bitem alokacji.
\item Linie 18--22 rozbijają nagłówek bloku na wartości pomocnicze: rozmiar nagłówka, rozmiar wyrównany, bit zajętości, rozmiar całego bloku i część użytkownika.
\item Linie 24--27 wypisują te same wartości tylko w wariancie hostowym; na RV32I makro \texttt{TRACE\_PRINTF} znika.
\item Wynik sprawdzaj w \texttt{g\_header\_size}, \texttt{g\_allocated\_bit\_set}, \texttt{g\_total\_block\_bytes} i \texttt{g\_user\_bytes}.
\end{itemize}
}{
W ASM szukaj operacji ustawiania najwyższego bitu rozmiaru oraz zapisów do symboli globalnych \texttt{g\_*}. Dostęp do pól \texttt{block.next} i \texttt{block.size} powinien być zwykłym zapisem pod adres bazowy stosu z przesunięciem.
}
\TaskSection{02}{task02_free_list}{lista wolnych bloków}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 7--16 implementują sumowanie rozmiarów bloków na liście.
\item Linie 18--28 przechodzą po tej samej liście i zapamiętują największy blok.
\item Linie 30--39 liczą liczbę węzłów listy.
\item Linie 43--55 budują ręcznie listę \texttt{start -> a -> b -> c -> NULL}.
\item Linie 57--59 uruchamiają trzy funkcje pomiarowe i zapisują wyniki w \texttt{volatile}.
\end{itemize}
}{
W ASM zwróć uwagę na pętle po wskaźniku \texttt{block = block->next}. To jest podstawowy idiom przechodzenia po liście jednokierunkowej, widoczny jako ładowanie pola \texttt{next} i skok warunkowy.
}
\TaskSection{03}{task03_heap_init}{inicjalizacja sterty}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--7 definiują bufor sterty, węzeł startowy listy i wskaźnik końcowego wartownika.
\item Linie 20--23 wyrównują początek sterty i obcinają dostępną długość do wielokrotności wyrównania.
\item Linie 25--28 ustawiają adres i pola końcowego wartownika.
\item Linie 30--34 tworzą pierwszy wolny blok i podpinają go za \texttt{start}.
\item Linie 39--43 wykonują inicjalizację i zapisują trzy wartości kontrolne.
\end{itemize}
}{
W ASM szukaj maskowania dolnych bitów adresu i rozmiaru. Warto też porównać zapisy do \texttt{first->next}, \texttt{first->size}, \texttt{start.next} i \texttt{start.size}; każdy z nich jest dostępem do pola struktury.
}
\TaskSection{04}{task04_alignment}{wyrównanie}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--6 definiują wyniki pokazujące wpływ wyrównania.
\item Linie 14--16 liczą rzeczywiste rozmiary bloków dla żądań 1, 9 i 17 bajtów, po dodaniu nagłówka.
\item Linie 18--20 biorą celowo nie-wyrównany adres \texttt{\&bytes[1]} i wyliczają przesunięcie do kolejnej granicy wyrównania.
\item Linie 22--24 są hostowym wypisem kontrolnym.
\end{itemize}
}{
W ASM znajdź operacje \texttt{and} używane do testowania dolnych bitów oraz dodawanie brakującej liczby bajtów do wyrównania. To jest ten sam mechanizm, którego używa alokator dla rozmiaru bloku i początku sterty.
}
\TaskSection{05}{task05_malloc_first_fit}{\texttt{first fit}}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 13--30 inicjalizują stertę z jednym wolnym blokiem oraz wartownikiem końca.
\item Linie 38--40 zamieniają żądanie użytkownika na całkowity rozmiar bloku i ustawiają początek przeszukiwania listy.
\item Linie 42--51 realizują algorytm \texttt{first fit}: idą po liście do pierwszego bloku o wystarczającym rozmiarze.
\item Linie 44--47 zdejmują znaleziony blok z listy wolnych, oznaczają go jako zajęty i zwracają adres danych użytkownika.
\item Linie 61--67 wykonują alokację i zapisują, czy się udała, jaki rozmiar dostał blok i ile wolnego miejsca zostało na liście.
\end{itemize}
}{
W ASM obserwuj porównanie \texttt{block->size >= total}, zmianę \texttt{previous->next} oraz dodanie \texttt{sizeof(HeapBlock)} do adresu bloku przed zwróceniem wskaźnika użytkownika.
}
\TaskSection{06}{task06_split_block}{podział bloku}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--12 definiują rozmiar sterty, minimalny sensowny rozmiar reszty i zmienne wynikowe.
\item Linie 14--28 tworzą jeden duży wolny blok.
\item Linie 38--44 szukają bloku pasującego do żądania i zapamiętują jego pierwotny rozmiar.
\item Linie 45--50 wykonują podział: nowy blok \texttt{remainder} zaczyna się pod adresem \texttt{block + total}.
\item Linie 51--56 obsługują przypadek bez podziału, oznaczają blok jako zajęty i zwracają adres użytkownika.
\item Linie 65--73 liczą pozostałe wolne bloki, a linie 81--87 zapisują wynik demonstracji.
\end{itemize}
}{
W ASM szukaj arytmetyki adresów dla \texttt{remainder}: adres bloku plus \texttt{total}. To najważniejszy fragment pokazujący, że dzielenie bloku jest tylko przesunięciem wskaźnika i zapisaniem nowego nagłówka.
}
\TaskSection{07}{task07_free_insert}{\texttt{free} i wstawianie}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 11--20 wstawiają blok do listy w miejscu wynikającym z porządku adresów.
\item Linie 22--32 modelują \texttt{free}: z adresu użytkownika wracają do nagłówka bloku, czyszczą bit alokacji i wywołują wstawianie.
\item Linie 34--54 zawierają funkcje kontrolne liczące węzły oraz łączny rozmiar wolnej pamięci.
\item Linie 63--76 budują układ \texttt{a}, \texttt{b}, \texttt{c}, gdzie \texttt{b} jest zajętym blokiem między dwoma wolnymi.
\item Linie 78--83 zwalniają \texttt{b} i sprawdzają, czy lista ma kolejność \texttt{a -> b -> c -> end}.
\end{itemize}
}{
W ASM zwróć uwagę na porównywanie adresów w pętli z linii 15. To porządkowanie po adresie jest przygotowaniem do scalania bloków w następnym tasku.
}
\TaskSection{08}{task08_coalescing}{scalanie bloków}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 11--17 znajdują miejsce w liście, w którym powinien znaleźć się zwracany blok.
\item Linie 19--25 sprawdzają sąsiedztwo z prawym blokiem; jeśli adres końca zwracanego bloku jest adresem następnego, rozmiary są łączone.
\item Linie 27--32 sprawdzają sąsiedztwo z lewym blokiem i ewentualnie dołączają scalony wynik do lewej strony.
\item Linie 35--44 liczą końcową liczbę wolnych bloków.
\item Linie 52--65 budują trzy sąsiednie bloki w pamięci, a linia 67 uruchamia scalanie środkowego bloku.
\item Linie 69--71 zapisują, czy po scaleniu został jeden blok i jaki ma rozmiar.
\end{itemize}
}{
W ASM szukaj testu \texttt{(uint8\_t *)block + block->size == iterator->next}. To jest sedno scalania: alokator nie porównuje indeksów, tylko rzeczywiste adresy końca i początku bloków.
}
\TaskSection{09}{task09_config_macros}{makra konfiguracyjne}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--9 definiują wartości domyślne tylko wtedy, gdy nie zostały podane z zewnątrz przez kompilator.
\item Linia 11 tworzy wartość pochodną od konfiguracji i wyrównania.
\item Linie 20--22 zapisują wartości makr do zmiennych \texttt{volatile}, żeby były widoczne w debugerze.
\item Linie 24--28 pokazują kompilację warunkową: host ustawia \texttt{g\_trace\_is\_host\_only} na 1, RV32I na 0.
\item Linie 30--32 wypisują wynik tylko w wariancie hostowym.
\end{itemize}
}{
W ASM dla RV32I nie powinno być wywołania \texttt{printf}. Sprawdź też, że makra konfiguracyjne nie są zmiennymi w pamięci, tylko stałymi wstawionymi do kodu przez preprocesor.
}
\TaskSection{10}{task10_heap4_demo}{mini \texttt{heap\_4}}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 18--38 to funkcje diagnostyczne przechodzące po liście wolnych bloków.
\item Linie 40--62 wstawiają blok i scalają go z prawym oraz lewym sąsiadem.
\item Linie 64--88 inicjalizują stertę: wyrównują początek, ustawiają wartownika, pierwszy wolny blok i liczniki wolnej pamięci.
\item Linie 90--131 implementują alokację: sprawdzają rozmiar żądania, szukają bloku, dzielą go lub zdejmują w całości z listy, oznaczają jako zajęty i aktualizują minimum wolnej pamięci.
\item Linie 133--149 implementują zwalnianie: wracają z adresu użytkownika do nagłówka, sprawdzają bit alokacji, przywracają rozmiar i scalają blok.
\item Linie 157--169 wykonują sekwencję \texttt{malloc/free} i zapisują stan końcowy alokatora.
\end{itemize}
}{
W ASM porównaj trzy fragmenty: inicjalizację listy, pętlę szukania wolnego bloku oraz wywołanie \texttt{insert\_block\_merge}. To pokazuje, że pełny model składa się z prostych operacji wskaźnikowych poznanych w poprzednich taskach.
}
\section*{Punkt startowy — tego już nie powtarzamy}
\section{Polecenia}
\begin{enumerate}[leftmargin=1.6em]
\item Zbuduj kartę dla RV32I poleceniem \texttt{make tasks}.
\item Zbuduj wariant hostowy poleceniem \texttt{make host}; wypisywanie działa tylko przez \texttt{HOST\_PRINTF}.
\item W tasku 6 zmień próg \texttt{MIN\_SPLIT\_SIZE} i sprawdź, kiedy blok nie jest dzielony.
\item W tasku 8 zmień rozmiary bloków tak, aby scalanie działało tylko z prawym sąsiadem.
\item W tasku 10 dodaj większą alokację, która ma się nie udać, i zapisz status w nowej zmiennej \texttt{volatile}.
Z poprzedniej karty działa projekt:
\begin{lstlisting}
allocbuf[64]; allocp = allocbuf;
alloc_local(5) -> offset 0
alloc_local(7) -> offset 5
allocp -> offset 12
alloc_local(0) i OOM -> NULL; kursor pozostaje na 12
\end{lstlisting}
\noindent\fcolorbox{accent}{accentlight}{%
\begin{minipage}{.94\textwidth}
\textbf{Pytanie projektowe.} Jak zwolnić obszar 5 B spod offsetu 0, zachować
późniejszy obszar 7 B spod offsetu 5 i ponownie wykorzystać powstałą dziurę?
W naszym projekcie nie istnieje \texttt{free}; reset odzyskuje całą arenę.
Samo cofnięcie kursora zniszczyłoby również późniejsze przydziały.
\end{minipage}}
\section*{Cel lekcji}
Potrafię wskazać trzy elementy dodane przez \texttt{heap\_4} do alokatora
liniowego --- \textbf{nagłówek bloku, listę wolnych bloków i scalanie} --- oraz
potwierdzić ich działanie na prawdziwym \texttt{heap\_4.c}.
\section*{Plan 30 minut}
\begin{tabularx}{\textwidth}{@{}p{1.55cm}p{3.0cm}X@{}}
\toprule
Czas & Tryb & Wynik \\
\midrule
0--3 & problem K\&R & nazwać, dlaczego jeden kursor nie zwolni obszaru ze środka \\
3--4 & kontekst & FreeRTOS linkuje jedną implementację; tutaj pracujemy tylko z \texttt{heap\_4} \\
4--9 & Demo 1 & przewidzieć nagłówek, first-fit i split \\
9--16 & Demo 2 & przewidzieć listę adresową i scalenie \texttt{2 -> 1} \\
16--27 & Task 3 RUN & osiem checkpointów E01--E08 prawdziwego \texttt{heap\_4.c} \\
27--30 & wyjście & odróżnić total free, largest free i minimum-ever \\
\bottomrule
\end{tabularx}
\vfill
\noindent\textbf{Jedna minuta kontekstu:} \texttt{heap\_1} przypomina rosnący
kursor bez \texttt{free}; \texttt{heap\_2} nie scala; \texttt{heap\_3} używa
alokatora biblioteki C; \texttt{heap\_5} rozszerza mechanikę \texttt{heap\_4}
na wiele regionów. Nie są to tematy tej lekcji.
\newpage
\section{Most: od K\&R 5.4 do \texttt{heap\_4}}
\begin{tabularx}{\textwidth}{@{}p{3.5cm}p{4.8cm}X@{}}
\toprule
K\&R 5.4 — co mamy & Ograniczenie & Co dodaje \texttt{heap\_4} \\
\midrule
\texttt{allocbuf[64]}, \texttt{allocp} & istnieje tylko wolny ogon areny & arena podzielona na opisane bloki \\
wynik to surowy adres & później nie znamy rozmiaru obszaru & nagłówek \texttt{BlockLink\_t} przed payloadem \\
przydział przesuwa kursor & nie wykorzystamy dziury & lista wolnych bloków i first-fit \\
reset całej areny & brak zwolnienia jednego obszaru & \texttt{vPortFree()} wskazanego bloku \\
brak relacji między dziurami & sąsiednie dziury są rozdzielone & porządek adresowy i coalescing \\
sukces albo \texttt{NULL} & brak miary fragmentacji & free, largest, block count, minimum-ever \\
\bottomrule
\end{tabularx}
\subsection*{Nagłówek realnego bloku}
\begin{lstlisting}
typedef struct A_BLOCK_LINK {
struct A_BLOCK_LINK *pxNextFreeBlock;
size_t xBlockSize; /* MSB oznacza blok przydzielony */
} BlockLink_t;
[ BlockLink_t | payload zwracany przez pvPortMalloc() ]
^ metadata ^ adres widziany przez aplikacje
\end{lstlisting}
\textbf{Granica modelu:} Task 1--2 używają uproszczonego nagłówka
\texttt{\{next,size\}}. Bit allocated i pełne zabezpieczenia oglądamy dopiero
w upstream \texttt{heap\_4.c}.
\section{Demo 1 — pierwszy pasujący blok i split (5 minut)}
Na RP2350/RV32 nagłówek modelu ma 8 B. Dla żądania 13 B:
\[
\texttt{wanted}=\operatorname{align8}(8+13)=24\ \mathrm{B}
\]
\begin{lstlisting}
free list przed: [ block 16 B ] -> [ block 256 B ] -> END
request po naglowku i align8: 24 B
\end{lstlisting}
\textbf{Predykcja — wpisz przed odsłonięciem:}
\begin{enumerate}
\item Pierwszy pasujący blok ma \blank{1.5cm} B.
\item Blok przydzielony ma \blank{1.5cm} B, a remainder \blank{1.5cm} B.
\item Równość zachowania areny: \blank{2cm} + \blank{2cm} = 256 B.
\end{enumerate}
\noindent\textbf{Sprawdzenie po predykcji:}
\texttt{16 B} nie mieści żądania, więc first-fit wybiera \texttt{256 B}; split
daje \texttt{24 B + 232 B}. Realny \texttt{heap\_4} dzieli blok tylko wtedy,
gdy reszta jest większa od minimalnego rozmiaru bloku.
\noindent\textbf{Nie uruchamiamy osobnego debugowania Task 1.} Model służy do
policzenia geometrii; centralny RUN jest w Task 3.
\newpage
\section{Demo 2 — lista adresowa i coalescing (7 minut)}
\begin{center}
\texttt{[ A:48 FREE ][ B:48 USED ][ C:48 FREE ]}
\end{center}
\begin{lstlisting}
pamiec: A -------- B -------- C
free list: A ------------------> C -> END
\end{lstlisting}
\textbf{Predykcja:} po \texttt{vPortFree(B)} narysuj listę i uzupełnij:
\begin{tabular}{@{}lccc@{}}
\toprule
Stan & liczba wolnych bloków & suma wolna & największy blok \\
\midrule
przed zwolnieniem B & \blank{1.2cm} & \blank{1.2cm} & \blank{1.2cm} \\
po zwolnieniu B & \blank{1.2cm} & \blank{1.2cm} & \blank{1.2cm} \\
\bottomrule
\end{tabular}
\subsection*{Odsłoń po zapisaniu predykcji}
\begin{enumerate}
\item Wstaw B między A i C według adresu.
\item \texttt{end(B) == address(C)}: połącz B z C.
\item \texttt{end(A) == address(BC)}: połącz A z BC.
\end{enumerate}
\begin{lstlisting}
przed: A:48 -> C:48 -> END blocks=2 total=96 largest=48
po: A:144 -> END blocks=1 total=144 largest=144
\end{lstlisting}
Porządek po adresie sprawia, że fizycznych sąsiadów można sprawdzić podczas
jednego wstawienia. Uproszczony Task 2 scala prawą, a następnie lewą stronę.
Właściwy \texttt{prvInsertBlockIntoFreeList()} z FreeRTOS V11.3.0 wykonuje
te same dwa testy w kolejności: poprzedni/lewy, następnie kolejny/prawy blok.
\section{Task 3 — jedyny centralny RUN (11 minut)}
Obraz i sesja muszą być przygotowane przed lekcją. Nie kompilujemy ani nie
flashujemy w czasie tych 30 minut.
\begin{lstlisting}[language=bash]
# wykonane przed lekcja:
stemctl debug rp2350 freertos heap4 3 --device /dev/bus/usb/BBB/DDD
\end{lstlisting}
\begin{lstlisting}
RESET/INIT
-> heap4_reset_checkpoint # E01: free=4080
-> heap4_alloc_a_checkpoint # E02: free=4032, used=48
-> heap4_alloc_b_checkpoint # E03: free=3968, used=64
-> heap4_alloc_c_checkpoint # E04: free=3936, used=32
-> heap4_free_a_checkpoint # E05: free=3984, blocks=2
-> heap4_fragmented_checkpoint # E06: free=4016, blocks=2
-> heap4_coalesced_checkpoint # E07: free=4080, blocks=1
-> task03_debug_checkpoint # E08: OOM, hook, PASS
\end{lstlisting}
\textbf{E01--E04:} sprawdź, że zużycie obejmuje wyrównany nagłówek całego
bloku, a nie tylko payload. \textbf{E05--E06:} porównaj sumę wolną z largest.
Przed przejściem z E06 do E07 ustaw tymczasowy breakpoint na
\texttt{prvInsertBlockIntoFreeList()} i sprawdź oba testy sąsiedztwa.
\textbf{E07--E08:} potwierdź odzyskanie areny, minimum-ever, OOM i hook.
\newpage
\section{Dowód na Pico 2 W / RP2350}
\textbf{Ważne:} \texttt{heap\_4} jest plikiem wybranym i linkowanym przez
projekt. Nie jest sprzętowym heapem ani peryferium RP2350. Pico 2 W dostarcza
realną pamięć SRAM, w której obserwujemy ten sam algorytm.
\subsection*{Minimalny widok GDB}
\begin{lstlisting}
p g_fragmented_stats
p g_final_stats
p g_a_consumed
p g_b_consumed
p g_c_consumed
p xFreeBytesRemaining
p xMinimumEverFreeBytesRemaining
p xStart
p pxEnd
x/160bx ucHeap
\end{lstlisting}
\subsection*{Tabela obserwacji}
\begin{tabularx}{\textwidth}{@{}p{4.2cm}p{3.0cm}X@{}}
\toprule
Wielkość & Odczyt & Warunek \\
\midrule
\texttt{initial\_free} & \blank{2.3cm} & punkt odniesienia \\
\texttt{after\_allocations} & \blank{2.3cm} & mniejsze niż initial \\
E06: free blocks & \blank{2.3cm} & równe 2 \\
E06: total / largest & \blank{2.3cm} & largest mniejsze niż total \\
E07: final free & \blank{2.3cm} & równe initial \\
E07: free blocks & \blank{2.3cm} & równe 1 \\
minimum-ever & \blank{2.3cm} & nie rośnie po free \\
OOM / hook / asserts / pass & \blank{2.3cm} & \texttt{1 / 1 / 0 / 1} \\
\bottomrule
\end{tabularx}
\section*{Wyjście — trzy minuty}
\begin{enumerate}
\item Dlaczego jeden \texttt{allocp} nie wystarcza do zwolnienia obszaru
spod offsetu 0 przy zachowaniu obszaru spod offsetu 5?\\[.35em]
\blank{.96\linewidth}
\item Jak \texttt{vPortFree()} znajduje rozmiar payloadu, skoro dostaje tylko
jego adres?\\[.35em]
\blank{.96\linewidth}
\item Dlaczego 96 wolnych bajtów w dwóch blokach nie pozwala przydzielić
jednego bloku 80 B?\\[.35em]
\blank{.96\linewidth}
\end{enumerate}
\subsection*{Zaliczenie}
\begin{itemize}
\item \checkline wymieniam: header, free list, coalescing;
\item \checkline przewiduję \texttt{2 bloki -> 1 blok};
\item \checkline odróżniam payload, metadata allocatora i stos \texttt{sp};
\item \checkline odróżniam current free, largest free i minimum-ever;
\item \checkline Task 3 kończy się
\texttt{aligned=1 oom=1 hooks=1 asserts=0 pass=1}.
\end{itemize}
\vfill
\noindent\textbf{Poza lekcją:} pełne czytanie upstream \texttt{heap\_4.c},
osobne uruchomienie Task 1--2 na AMD64/Hazard3, samodzielny deploy RP2350 oraz
porównanie z \texttt{heap\_1}, \texttt{heap\_2}, \texttt{heap\_3} i
\texttt{heap\_5}.
\end{document}
+1 -1
View File
@@ -1 +1 @@
e69aaa6d61af39003186415f3581d519e7a290bd
48eccb5e2497b692892c7b24ffa1ed7a6f8480b8
+51
View File
@@ -0,0 +1,51 @@
# FC01 `heap_4` — stan weryfikacji
## Stan na 2026-07-20
Karta ma jeden centralny eksperyment na niezmodyfikowanym
`heap_4.c` z FreeRTOS-Kernel V11.3.0. Przebieg jest podzielony na osiem
deterministycznych checkpointów E01E08 i działa na RV32I/Hazard3.
## Potwierdzone na Hazard3
| Event | Stan | Dowód |
|---|---|---|
| E01 | arena po inicjalizacji | `free=largest=4080`, 1 blok |
| E02 | `alloc A(24)` | zużyto 48, zostało 4032 |
| E03 | `alloc B(40)` | zużyto 64, zostało 3968 |
| E04 | `alloc C(16)` | zużyto 32, zostało 3936 |
| E05 | `free(A)` | 3984, 2 wolne bloki |
| E06 | `free(C)` | 4016, 2 bloki, `free > largest` |
| E07 | `free(B)` | `free=largest=4080`, 1 blok |
| E08 | kontrolowany OOM | `NULL`, hook=1, asserts=0, PASS=1 |
Na RV32 `sizeof(BlockLink_t) == 8`, ale `portBYTE_ALIGNMENT == 16`, więc
prywatne `xHeapStructSize == 16`. To wyjaśnia zużycie 48/64/32 bajtów.
## Diagramy i iteracja
- A1 CONTEXT: pięć kroków CODE — granice odpowiedzialności.
- A2 STRUCTURE: jeden krok CODE i pięć kroków RUN — nagłówek, split,
lista adresowa oraz scalenie lewego i prawego sąsiada.
- A5 FLOW: osiem kroków RUN mapowanych 1:1 na E01E08.
- A6 STATE: pięć reprezentatywnych stanów E01, E04, E06, E07 i E08.
- A7 RUNTIME: tożsamość źródła/ELF, pamięć `ucHeap` i końcowy werdykt.
- A3, A4 i A8 są jawnie pominięte, ponieważ dublowałyby A2/A5.
Szczegółowa kolejność komend, obserwacji i kryteriów znajduje się w
[`hazard3-iteration-plan.md`](hazard3-iteration-plan.md).
## Wykonane testy
- testy hostowe Task 13;
- budowa RV32I i kontrola symboli ELF;
- Hazard3 RTL: zakończenie kodem 0 po 27308 cyklach;
- osiem checkpointów obecnych w ELF;
- walidacja JSON i odwołań `code_ref`/`snapshot_ref`;
- kontrola PlantUML, SVG, HTML, TeX i siedmiostronicowego PDF;
- TypeScript/React typecheck generatora.
## Pozostaje poza zakresem tej iteracji
- próba na fizycznym RP2350/Pico 2 W;
- commit i publikacja — wymagają osobnego polecenia.
+68
View File
@@ -0,0 +1,68 @@
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#define configCPU_CLOCK_HZ ( 150000000UL )
#define configTICK_RATE_HZ ( 1000U )
#define configMTIME_BASE_ADDRESS ( 0xC0000100UL )
#define configMTIMECMP_BASE_ADDRESS ( 0xC0000108UL )
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configUSE_IDLE_HOOK 0
#define configUSE_PASSIVE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configMAX_PRIORITIES 4
#define configMINIMAL_STACK_SIZE 128
#define configMAX_TASK_NAME_LEN 12
#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_32_BITS
#define configIDLE_SHOULD_YIELD 1
#define configNUMBER_OF_CORES 1
#define configTICK_CORE 0
#define configUSE_CORE_AFFINITY 0
#define configRUN_MULTIPLE_PRIORITIES 0
#define configSUPPORT_PICO_SYNC_INTEROP 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configSUPPORT_STATIC_ALLOCATION 0
#define configTOTAL_HEAP_SIZE ( 4096U )
#define configAPPLICATION_ALLOCATED_HEAP 1
#define configUSE_MALLOC_FAILED_HOOK 1
#define configHEAP_CLEAR_MEMORY_ON_FREE 0
#define configENABLE_HEAP_PROTECTOR 0
#define configUSE_TIMERS 0
#define configUSE_MUTEXES 0
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configUSE_TASK_NOTIFICATIONS 1
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 0
#define configGENERATE_RUN_TIME_STATS 0
#define configUSE_CO_ROUTINES 0
#define configUSE_NEWLIB_REENTRANT 0
#define configUSE_POSIX_ERRNO 0
#define configCHECK_FOR_STACK_OVERFLOW 0
#define INCLUDE_vTaskDelay 0
#define INCLUDE_vTaskDelayUntil 0
#define INCLUDE_vTaskDelete 0
#define INCLUDE_vTaskSuspend 0
#define INCLUDE_xTaskGetSchedulerState 0
#define configENABLE_FPU 0
#define configENABLE_VPU 0
#if !defined(PICO_RP2350)
#define configISR_STACK_SIZE_WORDS 128
#endif
#ifndef __ASSEMBLER__
void heap4_lab_assert(const char *file, int line);
#define configASSERT(condition) \
do { if (!(condition)) heap4_lab_assert(__FILE__, __LINE__); } while (0)
#else
#define configASSERT(condition)
#endif
#endif
@@ -0,0 +1,15 @@
#ifndef FREERTOS_RISC_V_CHIP_SPECIFIC_EXTENSIONS_H
#define FREERTOS_RISC_V_CHIP_SPECIFIC_EXTENSIONS_H
/* Hazard3 adds no architectural registers to the base RV32I context. */
#define portasmHAS_MTIME 1
#define portasmHAS_SIFIVE_CLINT 0
#define portasmADDITIONAL_CONTEXT_SIZE 0
.macro portasmSAVE_ADDITIONAL_REGISTERS
.endm
.macro portasmRESTORE_ADDITIONAL_REGISTERS
.endm
#endif
+4
View File
@@ -0,0 +1,4 @@
#ifndef HEAP4_FREESTANDING_STDLIB_H
#define HEAP4_FREESTANDING_STDLIB_H
#include <stddef.h>
#endif
+7
View File
@@ -0,0 +1,7 @@
#ifndef HEAP4_FREESTANDING_STRING_H
#define HEAP4_FREESTANDING_STRING_H
#include <stddef.h>
void *memcpy(void *destination, const void *source, size_t count);
void *memset(void *destination, int value, size_t count);
size_t strlen(const char *text);
#endif
+50
View File
@@ -0,0 +1,50 @@
#ifndef HEAP4_HOST_FREERTOS_H
#define HEAP4_HOST_FREERTOS_H
#include <stddef.h>
#include <stdint.h>
typedef int BaseType_t;
typedef uintptr_t portPOINTER_SIZE_TYPE;
typedef struct xHeapStats {
size_t xAvailableHeapSpaceInBytes;
size_t xSizeOfLargestFreeBlockInBytes;
size_t xSizeOfSmallestFreeBlockInBytes;
size_t xNumberOfFreeBlocks;
size_t xMinimumEverFreeBytesRemaining;
size_t xNumberOfSuccessfulAllocations;
size_t xNumberOfSuccessfulFrees;
} HeapStats_t;
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configTOTAL_HEAP_SIZE ((size_t)4096U)
#define configAPPLICATION_ALLOCATED_HEAP 1
#define configUSE_MALLOC_FAILED_HOOK 1
#define configHEAP_CLEAR_MEMORY_ON_FREE 0
#define configENABLE_HEAP_PROTECTOR 0
#define portBYTE_ALIGNMENT 8U
#define portBYTE_ALIGNMENT_MASK (portBYTE_ALIGNMENT - 1U)
#define portMAX_DELAY (~(size_t)0U)
#define PRIVILEGED_DATA
#define PRIVILEGED_FUNCTION
#define mtCOVERAGE_TEST_MARKER() do { } while (0)
#define traceMALLOC(pointer, size) do { (void)(pointer); (void)(size); } while (0)
#define traceFREE(pointer, size) do { (void)(pointer); (void)(size); } while (0)
#define taskENTER_CRITICAL() do { } while (0)
#define taskEXIT_CRITICAL() do { } while (0)
void heap4_lab_assert(const char *file, int line);
void vApplicationMallocFailedHook(void);
void *pvPortMalloc(size_t wanted_size);
void vPortFree(void *pointer);
size_t xPortGetFreeHeapSize(void);
size_t xPortGetMinimumEverFreeHeapSize(void);
void vPortGetHeapStats(HeapStats_t *stats);
void vPortHeapResetState(void);
#define configASSERT(condition) \
do { if (!(condition)) heap4_lab_assert(__FILE__, __LINE__); } while (0)
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef HEAP4_HOST_TASK_H
#define HEAP4_HOST_TASK_H
#include "FreeRTOS.h"
void vTaskSuspendAll(void);
BaseType_t xTaskResumeAll(void);
#endif
File diff suppressed because it is too large Load Diff
+1139
View File
File diff suppressed because it is too large Load Diff
-61
View File
@@ -1,61 +0,0 @@
#ifndef HEAP4_COMMON_H
#define HEAP4_COMMON_H
#include <stddef.h>
#include <stdint.h>
#ifdef HOST_PRINTF
#include <stdio.h>
#define TRACE_PRINTF(...) printf(__VA_ARGS__)
#else
#define TRACE_PRINTF(...) ((void)0)
#endif
#define HEAP4_ARRAY_LEN(a) ((int)(sizeof(a) / sizeof((a)[0])))
#define HEAP4_ALIGNMENT ((size_t)8)
#define HEAP4_ALIGNMENT_MASK (HEAP4_ALIGNMENT - 1U)
#define HEAP4_ALLOCATED_BIT ((size_t)1U << (sizeof(size_t) * 8U - 1U))
typedef struct HeapBlock {
struct HeapBlock *next;
size_t size;
} HeapBlock;
static inline size_t heap4_align_up(size_t value)
{
if ((value & HEAP4_ALIGNMENT_MASK) != 0U)
value += HEAP4_ALIGNMENT - (value & HEAP4_ALIGNMENT_MASK);
return value;
}
static inline uintptr_t heap4_align_address(uintptr_t address)
{
uintptr_t mask;
mask = (uintptr_t)HEAP4_ALIGNMENT_MASK;
if ((address & mask) != 0U)
address += (uintptr_t)HEAP4_ALIGNMENT - (address & mask);
return address;
}
static inline size_t heap4_clear_allocated(size_t size)
{
return size & ~HEAP4_ALLOCATED_BIT;
}
static inline size_t heap4_mark_allocated(size_t size)
{
return size | HEAP4_ALLOCATED_BIT;
}
static inline int heap4_is_allocated(size_t size)
{
return (size & HEAP4_ALLOCATED_BIT) != 0U;
}
static inline int heap4_blocks_touch(HeapBlock *left, HeapBlock *right)
{
return (uint8_t *)left + heap4_clear_allocated(left->size) == (uint8_t *)right;
}
#endif
+25
View File
@@ -0,0 +1,25 @@
#ifndef HEAP4_MODEL_H
#define HEAP4_MODEL_H
#include <stddef.h>
#include <stdint.h>
#define MODEL_ALIGNMENT ((size_t)8U)
typedef struct ModelBlock ModelBlock;
struct ModelBlock {
ModelBlock *next;
size_t size;
};
static inline size_t model_align_up(size_t value)
{
return (value + MODEL_ALIGNMENT - 1U) & ~(MODEL_ALIGNMENT - 1U);
}
#if defined(HOST_PRINTF)
#include <stdio.h>
#endif
#endif
+72
View File
@@ -0,0 +1,72 @@
#include "heap4_model.h"
enum {
ARENA_BYTES = 256,
REQUEST_BYTES = 13,
WANTED_BYTES = (sizeof(ModelBlock) + REQUEST_BYTES + 7U) & ~7U
};
typedef struct {
ModelBlock allocated;
uint8_t payload[WANTED_BYTES - sizeof(ModelBlock)];
ModelBlock remainder;
uint8_t free_payload[ARENA_BYTES - WANTED_BYTES - sizeof(ModelBlock)];
} SplitArena;
typedef union {
uint64_t force_alignment;
SplitArena blocks;
} AlignedSplitArena;
AlignedSplitArena g_split_arena;
ModelBlock *g_free_head;
volatile size_t g_header_bytes;
volatile size_t g_wanted_bytes;
volatile size_t g_remainder_bytes;
volatile size_t g_payload_offset;
volatile int g_split_addresses_aligned;
volatile int g_task01_pass;
__attribute__((noinline)) void task01_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
SplitArena *arena = &g_split_arena.blocks;
uintptr_t base = (uintptr_t)&arena->allocated;
uintptr_t remainder = (uintptr_t)&arena->remainder;
arena->allocated.next = NULL;
arena->allocated.size = WANTED_BYTES;
arena->remainder.next = NULL;
arena->remainder.size = ARENA_BYTES - WANTED_BYTES;
arena->payload[0] = (uint8_t)'A';
g_free_head = &arena->remainder;
g_header_bytes = sizeof(ModelBlock);
g_wanted_bytes = WANTED_BYTES;
g_remainder_bytes = arena->remainder.size;
g_payload_offset = (size_t)((uintptr_t)&arena->payload[0] - base);
g_split_addresses_aligned =
(base % MODEL_ALIGNMENT) == 0U &&
(remainder % MODEL_ALIGNMENT) == 0U;
g_task01_pass =
g_wanted_bytes == model_align_up(sizeof(ModelBlock) + REQUEST_BYTES) &&
(size_t)(remainder - base) == g_wanted_bytes &&
g_remainder_bytes + g_wanted_bytes == ARENA_BYTES &&
g_payload_offset == sizeof(ModelBlock) &&
g_split_addresses_aligned;
task01_debug_checkpoint();
#if defined(HOST_PRINTF)
printf("header=%zu wanted=%zu remainder=%zu payload=%zu aligned=%d pass=%d\n",
g_header_bytes, g_wanted_bytes, g_remainder_bytes,
g_payload_offset, g_split_addresses_aligned, g_task01_pass);
#endif
return g_task01_pass ? 0 : 1;
}
-29
View File
@@ -1,29 +0,0 @@
#include "heap4_common.h"
volatile size_t g_header_size;
volatile size_t g_aligned_header_size;
volatile size_t g_allocated_bit_set;
volatile size_t g_user_bytes;
volatile size_t g_total_block_bytes;
int main(void)
{
HeapBlock block;
size_t requested;
requested = 13U;
block.next = 0;
block.size = heap4_mark_allocated(heap4_align_up(requested + sizeof(HeapBlock)));
g_header_size = sizeof(HeapBlock);
g_aligned_header_size = heap4_align_up(sizeof(HeapBlock));
g_allocated_bit_set = heap4_is_allocated(block.size);
g_total_block_bytes = heap4_clear_allocated(block.size);
g_user_bytes = g_total_block_bytes - sizeof(HeapBlock);
TRACE_PRINTF("header=%u aligned=%u allocated=%u total=%u user=%u\n",
(unsigned)g_header_size, (unsigned)g_aligned_header_size,
(unsigned)g_allocated_bit_set, (unsigned)g_total_block_bytes,
(unsigned)g_user_bytes);
return 0;
}
+136
View File
@@ -0,0 +1,136 @@
#include "heap4_model.h"
enum { BLOCK_BYTES = 48 };
typedef struct {
ModelBlock first;
uint8_t first_payload[BLOCK_BYTES - sizeof(ModelBlock)];
ModelBlock middle;
uint8_t middle_payload[BLOCK_BYTES - sizeof(ModelBlock)];
ModelBlock last;
uint8_t last_payload[BLOCK_BYTES - sizeof(ModelBlock)];
} CoalesceArena;
typedef union {
uint64_t force_alignment;
CoalesceArena blocks;
} AlignedCoalesceArena;
AlignedCoalesceArena g_coalesce_arena;
ModelBlock *g_free_head;
volatile size_t g_before_nodes;
volatile size_t g_before_total;
volatile size_t g_before_largest;
volatile size_t g_after_nodes;
volatile size_t g_after_total;
volatile size_t g_after_largest;
volatile int g_task02_pass;
static void insert_and_coalesce(ModelBlock *block)
{
ModelBlock *previous = NULL;
ModelBlock *current = g_free_head;
while (current != NULL && (uintptr_t)current < (uintptr_t)block)
{
previous = current;
current = current->next;
}
block->next = current;
if (current != NULL &&
(uint8_t *)block + block->size == (uint8_t *)current)
{
block->size += current->size;
block->next = current->next;
}
if (previous != NULL &&
(uint8_t *)previous + previous->size == (uint8_t *)block)
{
previous->size += block->size;
previous->next = block->next;
}
else if (previous != NULL)
{
previous->next = block;
}
else
{
g_free_head = block;
}
}
static void measure(size_t *nodes, size_t *total, size_t *largest)
{
ModelBlock *block;
*nodes = 0U;
*total = 0U;
*largest = 0U;
for (block = g_free_head; block != NULL; block = block->next)
{
(*nodes)++;
*total += block->size;
if (block->size > *largest)
{
*largest = block->size;
}
}
}
__attribute__((noinline)) void task02_fragmented_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void task02_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
CoalesceArena *arena = &g_coalesce_arena.blocks;
size_t nodes;
size_t total;
size_t largest;
arena->first.size = BLOCK_BYTES;
arena->middle.size = BLOCK_BYTES;
arena->last.size = BLOCK_BYTES;
g_free_head = NULL;
insert_and_coalesce(&arena->first);
insert_and_coalesce(&arena->last);
measure(&nodes, &total, &largest);
g_before_nodes = nodes;
g_before_total = total;
g_before_largest = largest;
task02_fragmented_checkpoint();
insert_and_coalesce(&arena->middle);
measure(&nodes, &total, &largest);
g_after_nodes = nodes;
g_after_total = total;
g_after_largest = largest;
g_task02_pass =
g_before_nodes == 2U && g_before_total == 96U &&
g_before_largest == 48U && g_after_nodes == 1U &&
g_after_total == 144U && g_after_largest == 144U &&
g_free_head == &arena->first && g_free_head->next == NULL;
task02_debug_checkpoint();
#if defined(HOST_PRINTF)
printf("before_nodes=%zu before_total=%zu before_largest=%zu "
"after_nodes=%zu after_total=%zu after_largest=%zu pass=%d\n",
g_before_nodes, g_before_total, g_before_largest,
g_after_nodes, g_after_total, g_after_largest, g_task02_pass);
#endif
return g_task02_pass ? 0 : 1;
}
-64
View File
@@ -1,64 +0,0 @@
#include "heap4_common.h"
volatile size_t g_free_total;
volatile size_t g_largest_free;
volatile int g_node_count;
static size_t list_total(HeapBlock *start)
{
HeapBlock *block;
size_t total;
total = 0U;
for (block = start->next; block != 0; block = block->next)
total += block->size;
return total;
}
static size_t list_largest(HeapBlock *start)
{
HeapBlock *block;
size_t largest;
largest = 0U;
for (block = start->next; block != 0; block = block->next)
if (block->size > largest)
largest = block->size;
return largest;
}
static int list_count(HeapBlock *start)
{
HeapBlock *block;
int count;
count = 0;
for (block = start->next; block != 0; block = block->next)
count++;
return count;
}
int main(void)
{
HeapBlock start;
HeapBlock a;
HeapBlock b;
HeapBlock c;
start.next = &a;
start.size = 0U;
a.next = &b;
a.size = 32U;
b.next = &c;
b.size = 80U;
c.next = 0;
c.size = 24U;
g_free_total = list_total(&start);
g_largest_free = list_largest(&start);
g_node_count = list_count(&start);
TRACE_PRINTF("free=%u largest=%u count=%d\n",
(unsigned)g_free_total, (unsigned)g_largest_free, g_node_count);
return 0;
}
+229
View File
@@ -0,0 +1,229 @@
#include <stddef.h>
#include <stdint.h>
#include "FreeRTOS.h"
#include "task.h"
#if defined(HOST_PRINTF)
#include <stdio.h>
#endif
uint8_t ucHeap[configTOTAL_HEAP_SIZE] __attribute__((aligned(portBYTE_ALIGNMENT)));
HeapStats_t g_initial_stats;
HeapStats_t g_after_a_stats;
HeapStats_t g_after_b_stats;
HeapStats_t g_after_allocations_stats;
HeapStats_t g_after_free_a_stats;
HeapStats_t g_fragmented_stats;
HeapStats_t g_final_stats;
volatile size_t g_initial_free;
volatile size_t g_after_a_free;
volatile size_t g_after_b_free;
volatile size_t g_after_allocations;
volatile size_t g_after_free_a;
volatile size_t g_fragmented_free;
volatile size_t g_final_free;
volatile size_t g_minimum_ever_free;
volatile size_t g_a_consumed;
volatile size_t g_b_consumed;
volatile size_t g_c_consumed;
void * volatile g_probe;
void * volatile g_a;
void * volatile g_b;
void * volatile g_c;
void * volatile g_too_large;
volatile int g_a_aligned;
volatile int g_b_aligned;
volatile int g_c_aligned;
volatile int g_allocations_aligned;
volatile int g_oom_is_null;
volatile int g_malloc_failed_hooks;
volatile int g_assert_failures;
volatile int g_rv32_layout_expected;
volatile int g_task03_pass;
void vApplicationMallocFailedHook(void)
{
g_malloc_failed_hooks++;
}
void heap4_lab_assert(const char *file, int line)
{
(void)file;
(void)line;
g_assert_failures++;
}
#if defined(HEAP4_HOST_ADAPTER)
void vTaskSuspendAll(void)
{
}
BaseType_t xTaskResumeAll(void)
{
return 0;
}
#endif
__attribute__((noinline)) void heap4_reset_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void heap4_alloc_a_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void heap4_alloc_b_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void heap4_alloc_c_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void heap4_free_a_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void heap4_fragmented_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void heap4_coalesced_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void task03_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
vPortHeapResetState();
g_probe = pvPortMalloc(1U);
if (g_probe != NULL)
{
vPortFree(g_probe);
}
g_initial_free = xPortGetFreeHeapSize();
vPortGetHeapStats(&g_initial_stats);
heap4_reset_checkpoint();
g_a = pvPortMalloc(24U);
g_after_a_free = xPortGetFreeHeapSize();
g_a_consumed = g_initial_free - g_after_a_free;
g_a_aligned =
g_a != NULL && ((uintptr_t)g_a % portBYTE_ALIGNMENT) == 0U;
vPortGetHeapStats(&g_after_a_stats);
heap4_alloc_a_checkpoint();
g_b = pvPortMalloc(40U);
g_after_b_free = xPortGetFreeHeapSize();
g_b_consumed = g_after_a_free - g_after_b_free;
g_b_aligned =
g_b != NULL && ((uintptr_t)g_b % portBYTE_ALIGNMENT) == 0U;
vPortGetHeapStats(&g_after_b_stats);
heap4_alloc_b_checkpoint();
g_c = pvPortMalloc(16U);
g_after_allocations = xPortGetFreeHeapSize();
g_c_consumed = g_after_b_free - g_after_allocations;
g_c_aligned =
g_c != NULL && ((uintptr_t)g_c % portBYTE_ALIGNMENT) == 0U;
g_allocations_aligned =
g_a_aligned && g_b_aligned && g_c_aligned;
vPortGetHeapStats(&g_after_allocations_stats);
heap4_alloc_c_checkpoint();
vPortFree(g_a);
g_after_free_a = xPortGetFreeHeapSize();
vPortGetHeapStats(&g_after_free_a_stats);
heap4_free_a_checkpoint();
vPortFree(g_c);
g_fragmented_free = xPortGetFreeHeapSize();
vPortGetHeapStats(&g_fragmented_stats);
heap4_fragmented_checkpoint();
vPortFree(g_b);
g_final_free = xPortGetFreeHeapSize();
g_minimum_ever_free = xPortGetMinimumEverFreeHeapSize();
vPortGetHeapStats(&g_final_stats);
heap4_coalesced_checkpoint();
g_too_large = pvPortMalloc(configTOTAL_HEAP_SIZE * 2U);
g_oom_is_null = g_too_large == NULL;
#if defined(__riscv) && (__riscv_xlen == 32)
g_rv32_layout_expected =
portBYTE_ALIGNMENT == 16U &&
g_initial_free == 4080U &&
g_after_a_free == 4032U && g_a_consumed == 48U &&
g_after_b_free == 3968U && g_b_consumed == 64U &&
g_after_allocations == 3936U && g_c_consumed == 32U &&
g_after_free_a == 3984U &&
g_fragmented_free == 4016U &&
g_final_free == 4080U;
#else
g_rv32_layout_expected = 1;
#endif
g_task03_pass =
g_probe != NULL && g_allocations_aligned &&
g_rv32_layout_expected &&
g_initial_stats.xNumberOfFreeBlocks == 1U &&
g_after_a_free < g_initial_free &&
g_after_b_free < g_after_a_free &&
g_initial_free > g_after_allocations &&
g_after_free_a > g_after_allocations &&
g_fragmented_free > g_after_free_a &&
g_final_free == g_initial_free &&
g_minimum_ever_free <= g_after_allocations &&
g_after_free_a_stats.xNumberOfFreeBlocks == 2U &&
g_fragmented_stats.xNumberOfFreeBlocks == 2U &&
g_fragmented_stats.xAvailableHeapSpaceInBytes >
g_fragmented_stats.xSizeOfLargestFreeBlockInBytes &&
g_final_stats.xNumberOfFreeBlocks == 1U &&
g_final_stats.xSizeOfLargestFreeBlockInBytes == g_initial_free &&
g_oom_is_null && g_malloc_failed_hooks == 1 &&
g_assert_failures == 0;
task03_debug_checkpoint();
#if defined(HOST_PRINTF)
printf("initial=%zu after_a=%zu after_b=%zu after_c=%zu "
"free_a=%zu fragmented=%zu final=%zu min=%zu "
"consumed=%zu/%zu/%zu aligned=%d oom=%d hooks=%d "
"asserts=%d pass=%d\n",
g_initial_free, g_after_a_free, g_after_b_free,
g_after_allocations, g_after_free_a, g_fragmented_free,
g_final_free, g_minimum_ever_free,
g_a_consumed, g_b_consumed, g_c_consumed,
g_allocations_aligned, g_oom_is_null,
g_malloc_failed_hooks, g_assert_failures, g_task03_pass);
#endif
return g_task03_pass ? 0 : 1;
}
-49
View File
@@ -1,49 +0,0 @@
#include "heap4_common.h"
#define HEAP_BYTES 160U
static uint8_t heap_area[HEAP_BYTES + HEAP4_ALIGNMENT];
static HeapBlock start;
static HeapBlock *end_marker;
volatile size_t g_initial_free_size;
volatile uintptr_t g_aligned_offset;
volatile int g_end_is_last;
static void heap_init(void)
{
uintptr_t raw;
uintptr_t aligned;
size_t available;
HeapBlock *first;
raw = (uintptr_t)&heap_area[0];
aligned = heap4_align_address(raw);
available = HEAP_BYTES - (size_t)(aligned - raw);
available &= ~HEAP4_ALIGNMENT_MASK;
first = (HeapBlock *)aligned;
end_marker = (HeapBlock *)(aligned + available - sizeof(HeapBlock));
end_marker->next = 0;
end_marker->size = 0U;
first->next = end_marker;
first->size = (uint8_t *)end_marker - (uint8_t *)first;
start.next = first;
start.size = 0U;
}
int main(void)
{
heap_init();
g_aligned_offset = (uintptr_t)start.next - (uintptr_t)&heap_area[0];
g_initial_free_size = start.next->size;
g_end_is_last = (start.next->next == end_marker) && (end_marker->next == 0);
TRACE_PRINTF("offset=%u free=%u end_last=%d\n",
(unsigned)g_aligned_offset, (unsigned)g_initial_free_size,
g_end_is_last);
return 0;
}
-26
View File
@@ -1,26 +0,0 @@
#include "heap4_common.h"
volatile size_t g_request_1;
volatile size_t g_request_9;
volatile size_t g_request_17;
volatile uintptr_t g_address_delta;
int main(void)
{
uint8_t bytes[32];
uintptr_t raw;
uintptr_t aligned;
g_request_1 = heap4_align_up(1U + sizeof(HeapBlock));
g_request_9 = heap4_align_up(9U + sizeof(HeapBlock));
g_request_17 = heap4_align_up(17U + sizeof(HeapBlock));
raw = (uintptr_t)&bytes[1];
aligned = heap4_align_address(raw);
g_address_delta = aligned - raw;
TRACE_PRINTF("r1=%u r9=%u r17=%u delta=%u\n",
(unsigned)g_request_1, (unsigned)g_request_9,
(unsigned)g_request_17, (unsigned)g_address_delta);
return 0;
}
-73
View File
@@ -1,73 +0,0 @@
#include "heap4_common.h"
#define HEAP_BYTES 128U
static uint8_t heap_area[HEAP_BYTES + HEAP4_ALIGNMENT];
static HeapBlock start;
static HeapBlock *end_marker;
volatile size_t g_allocated_size;
volatile size_t g_remaining_free;
volatile int g_allocation_ok;
static void heap_init_one_block(void)
{
uintptr_t raw;
uintptr_t aligned;
HeapBlock *first;
raw = (uintptr_t)&heap_area[0];
aligned = heap4_align_address(raw);
first = (HeapBlock *)aligned;
end_marker = (HeapBlock *)(aligned + 120U);
end_marker->next = 0;
end_marker->size = 0U;
first->next = end_marker;
first->size = (uint8_t *)end_marker - (uint8_t *)first;
start.next = first;
start.size = 0U;
}
static void *heap_malloc_whole_block(size_t wanted)
{
HeapBlock *previous;
HeapBlock *block;
size_t total;
total = heap4_align_up(wanted + sizeof(HeapBlock));
previous = &start;
block = start.next;
while (block != end_marker) {
if (block->size >= total) {
previous->next = block->next;
block->next = 0;
block->size = heap4_mark_allocated(block->size);
return (uint8_t *)block + sizeof(HeapBlock);
}
previous = block;
block = block->next;
}
return 0;
}
int main(void)
{
void *p;
HeapBlock *allocated;
heap_init_one_block();
p = heap_malloc_whole_block(24U);
allocated = (HeapBlock *)((uint8_t *)p - sizeof(HeapBlock));
g_allocation_ok = p != 0;
g_allocated_size = heap4_clear_allocated(allocated->size);
g_remaining_free = start.next->size;
TRACE_PRINTF("ok=%d allocated=%u remaining=%u\n",
g_allocation_ok, (unsigned)g_allocated_size,
(unsigned)g_remaining_free);
return 0;
}
-93
View File
@@ -1,93 +0,0 @@
#include "heap4_common.h"
#define HEAP_BYTES 192U
#define MIN_SPLIT_SIZE ((size_t)(sizeof(HeapBlock) * 2U))
static uint8_t heap_area[HEAP_BYTES + HEAP4_ALIGNMENT];
static HeapBlock start;
static HeapBlock *end_marker;
volatile size_t g_allocated_size;
volatile size_t g_split_free_size;
volatile int g_free_nodes;
static void heap_init_one_block(void)
{
uintptr_t aligned;
HeapBlock *first;
aligned = heap4_align_address((uintptr_t)&heap_area[0]);
first = (HeapBlock *)aligned;
end_marker = (HeapBlock *)(aligned + 176U);
end_marker->next = 0;
end_marker->size = 0U;
first->next = end_marker;
first->size = (uint8_t *)end_marker - (uint8_t *)first;
start.next = first;
start.size = 0U;
}
static void *heap_malloc_split(size_t wanted)
{
HeapBlock *previous;
HeapBlock *block;
HeapBlock *remainder;
size_t total;
size_t original;
total = heap4_align_up(wanted + sizeof(HeapBlock));
previous = &start;
block = start.next;
while (block != end_marker) {
if (block->size >= total) {
original = block->size;
if (original - total > MIN_SPLIT_SIZE) {
remainder = (HeapBlock *)((uint8_t *)block + total);
remainder->size = original - total;
remainder->next = block->next;
previous->next = remainder;
block->size = total;
} else {
previous->next = block->next;
}
block->next = 0;
block->size = heap4_mark_allocated(block->size);
return (uint8_t *)block + sizeof(HeapBlock);
}
previous = block;
block = block->next;
}
return 0;
}
static int count_free_nodes(void)
{
HeapBlock *block;
int count;
count = 0;
for (block = start.next; block != end_marker; block = block->next)
count++;
return count;
}
int main(void)
{
void *p;
HeapBlock *allocated;
heap_init_one_block();
p = heap_malloc_split(32U);
allocated = (HeapBlock *)((uint8_t *)p - sizeof(HeapBlock));
g_allocated_size = heap4_clear_allocated(allocated->size);
g_split_free_size = start.next->size;
g_free_nodes = count_free_nodes();
TRACE_PRINTF("allocated=%u split=%u nodes=%d\n",
(unsigned)g_allocated_size, (unsigned)g_split_free_size,
g_free_nodes);
return 0;
}
-88
View File
@@ -1,88 +0,0 @@
#include "heap4_common.h"
static uint8_t heap_area[192];
static HeapBlock start;
static HeapBlock *end_marker;
volatile int g_order_ok;
volatile int g_free_count;
volatile size_t g_total_free;
static void insert_block(HeapBlock *block)
{
HeapBlock *iterator;
for (iterator = &start; iterator->next < block; iterator = iterator->next)
;
block->next = iterator->next;
iterator->next = block;
}
static void heap_free_model(void *p)
{
HeapBlock *block;
if (p == 0)
return;
block = (HeapBlock *)((uint8_t *)p - sizeof(HeapBlock));
block->size = heap4_clear_allocated(block->size);
insert_block(block);
}
static int count_free(void)
{
HeapBlock *block;
int count;
count = 0;
for (block = start.next; block != end_marker; block = block->next)
count++;
return count;
}
static size_t total_free(void)
{
HeapBlock *block;
size_t total;
total = 0U;
for (block = start.next; block != end_marker; block = block->next)
total += block->size;
return total;
}
int main(void)
{
HeapBlock *a;
HeapBlock *b;
HeapBlock *c;
void *payload;
a = (HeapBlock *)&heap_area[0];
b = (HeapBlock *)&heap_area[64];
c = (HeapBlock *)&heap_area[128];
end_marker = (HeapBlock *)&heap_area[176];
start.next = a;
a->next = c;
c->next = end_marker;
end_marker->next = 0;
a->size = 40U;
b->size = heap4_mark_allocated(32U);
c->size = 48U;
end_marker->size = 0U;
payload = (uint8_t *)b + sizeof(HeapBlock);
heap_free_model(payload);
g_order_ok = start.next == a && a->next == b && b->next == c && c->next == end_marker;
g_free_count = count_free();
g_total_free = total_free();
TRACE_PRINTF("order=%d count=%d total=%u\n",
g_order_ok, g_free_count, (unsigned)g_total_free);
return 0;
}
-76
View File
@@ -1,76 +0,0 @@
#include "heap4_common.h"
static uint8_t heap_area[192];
static HeapBlock start;
static HeapBlock *end_marker;
volatile int g_free_count;
volatile size_t g_merged_size;
volatile int g_merged_with_right;
static void insert_block_merge(HeapBlock *block)
{
HeapBlock *iterator;
uint8_t *block_end;
for (iterator = &start; iterator->next < block; iterator = iterator->next)
;
block_end = (uint8_t *)block + block->size;
if (block_end == (uint8_t *)iterator->next) {
block->size += iterator->next->size;
block->next = iterator->next->next;
} else {
block->next = iterator->next;
}
if (iterator != &start && heap4_blocks_touch(iterator, block)) {
iterator->size += block->size;
iterator->next = block->next;
} else {
iterator->next = block;
}
}
static int count_free(void)
{
HeapBlock *block;
int count;
count = 0;
for (block = start.next; block != end_marker; block = block->next)
count++;
return count;
}
int main(void)
{
HeapBlock *left;
HeapBlock *middle;
HeapBlock *right;
left = (HeapBlock *)&heap_area[0];
middle = (HeapBlock *)&heap_area[48];
right = (HeapBlock *)&heap_area[96];
end_marker = (HeapBlock *)&heap_area[160];
start.next = left;
left->next = right;
right->next = end_marker;
end_marker->next = 0;
left->size = 48U;
middle->size = 48U;
right->size = 64U;
end_marker->size = 0U;
insert_block_merge(middle);
g_free_count = count_free();
g_merged_size = start.next->size;
g_merged_with_right = start.next->next == end_marker;
TRACE_PRINTF("count=%d size=%u merged_right=%d\n",
g_free_count, (unsigned)g_merged_size, g_merged_with_right);
return 0;
}
-34
View File
@@ -1,34 +0,0 @@
#include "heap4_common.h"
#ifndef CONFIG_TOTAL_HEAP_SIZE
#define CONFIG_TOTAL_HEAP_SIZE 256U
#endif
#ifndef CONFIG_SUPPORT_DYNAMIC_ALLOCATION
#define CONFIG_SUPPORT_DYNAMIC_ALLOCATION 1
#endif
#define CONFIG_ADJUSTED_HEAP_SIZE (CONFIG_TOTAL_HEAP_SIZE - HEAP4_ALIGNMENT)
volatile size_t g_total_heap;
volatile size_t g_adjusted_heap;
volatile int g_dynamic_enabled;
volatile int g_trace_is_host_only;
int main(void)
{
g_total_heap = CONFIG_TOTAL_HEAP_SIZE;
g_adjusted_heap = CONFIG_ADJUSTED_HEAP_SIZE;
g_dynamic_enabled = CONFIG_SUPPORT_DYNAMIC_ALLOCATION;
#ifdef HOST_PRINTF
g_trace_is_host_only = 1;
#else
g_trace_is_host_only = 0;
#endif
TRACE_PRINTF("heap=%u adjusted=%u dynamic=%d host_trace=%d\n",
(unsigned)g_total_heap, (unsigned)g_adjusted_heap,
g_dynamic_enabled, g_trace_is_host_only);
return 0;
}
-175
View File
@@ -1,175 +0,0 @@
#include "heap4_common.h"
#define HEAP_BYTES 256U
#define MIN_SPLIT_SIZE ((size_t)(sizeof(HeapBlock) * 2U))
static uint8_t heap_area[HEAP_BYTES + HEAP4_ALIGNMENT];
static HeapBlock start;
static HeapBlock *end_marker;
volatile int g_allocations_ok;
volatile int g_final_free_nodes;
volatile size_t g_final_free_total;
volatile size_t g_minimum_ever_free;
static size_t free_bytes_remaining;
static size_t minimum_ever_free;
static size_t list_total(void)
{
HeapBlock *block;
size_t total;
total = 0U;
for (block = start.next; block != end_marker; block = block->next)
total += block->size;
return total;
}
static int list_count(void)
{
HeapBlock *block;
int count;
count = 0;
for (block = start.next; block != end_marker; block = block->next)
count++;
return count;
}
static void insert_block_merge(HeapBlock *block)
{
HeapBlock *iterator;
uint8_t *block_end;
for (iterator = &start; iterator->next < block; iterator = iterator->next)
;
block_end = (uint8_t *)block + block->size;
if (block_end == (uint8_t *)iterator->next) {
block->size += iterator->next->size;
block->next = iterator->next->next;
} else {
block->next = iterator->next;
}
if (iterator != &start && heap4_blocks_touch(iterator, block)) {
iterator->size += block->size;
iterator->next = block->next;
} else {
iterator->next = block;
}
}
static void heap_init(void)
{
uintptr_t raw;
uintptr_t aligned;
size_t available;
HeapBlock *first;
raw = (uintptr_t)&heap_area[0];
aligned = heap4_align_address(raw);
available = HEAP_BYTES - (size_t)(aligned - raw);
available &= ~HEAP4_ALIGNMENT_MASK;
first = (HeapBlock *)aligned;
end_marker = (HeapBlock *)(aligned + available - sizeof(HeapBlock));
end_marker->next = 0;
end_marker->size = 0U;
first->next = end_marker;
first->size = (uint8_t *)end_marker - (uint8_t *)first;
start.next = first;
start.size = 0U;
free_bytes_remaining = first->size;
minimum_ever_free = free_bytes_remaining;
}
static void *heap_malloc_model(size_t wanted)
{
HeapBlock *previous;
HeapBlock *block;
HeapBlock *remainder;
size_t total;
size_t original;
if (wanted == 0U)
return 0;
total = heap4_align_up(wanted + sizeof(HeapBlock));
previous = &start;
block = start.next;
while (block != end_marker) {
if (block->size >= total) {
original = block->size;
if (original - total > MIN_SPLIT_SIZE) {
remainder = (HeapBlock *)((uint8_t *)block + total);
remainder->size = original - total;
remainder->next = block->next;
previous->next = remainder;
block->size = total;
} else {
previous->next = block->next;
total = original;
}
block->next = 0;
block->size = heap4_mark_allocated(block->size);
free_bytes_remaining -= total;
if (free_bytes_remaining < minimum_ever_free)
minimum_ever_free = free_bytes_remaining;
return (uint8_t *)block + sizeof(HeapBlock);
}
previous = block;
block = block->next;
}
return 0;
}
static void heap_free_model(void *p)
{
HeapBlock *block;
size_t size;
if (p == 0)
return;
block = (HeapBlock *)((uint8_t *)p - sizeof(HeapBlock));
if (!heap4_is_allocated(block->size))
return;
size = heap4_clear_allocated(block->size);
block->size = size;
free_bytes_remaining += size;
insert_block_merge(block);
}
int main(void)
{
void *a;
void *b;
void *c;
heap_init();
a = heap_malloc_model(24U);
b = heap_malloc_model(40U);
heap_free_model(a);
c = heap_malloc_model(16U);
heap_free_model(b);
heap_free_model(c);
g_allocations_ok = a != 0 && b != 0 && c != 0;
g_final_free_nodes = list_count();
g_final_free_total = list_total();
g_minimum_ever_free = minimum_ever_free;
TRACE_PRINTF("ok=%d nodes=%d free=%u min=%u\n",
g_allocations_ok, g_final_free_nodes,
(unsigned)g_final_free_total, (unsigned)g_minimum_ever_free);
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
schema: 1
targets:
native:
profile: native-amd64
actions: [build, test, run, debug]
hazard3-baremetal:
profile: hazard3-sim
actions: [build, test, run, debug]
rp2350-rv:
profile: rp2350
actions: [build, test, deploy, debug]
actions:
build: [bash, tools/card-action.sh, build]
test: [bash, tools/card-action.sh, test]
run: [bash, tools/card-action.sh, run]
deploy: [bash, tools/card-action.sh, deploy]
debug: [bash, tools/card-action.sh, debug]
artifacts:
directory: .stem/artifacts
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
elf="${1:?usage: check_task03_elf.sh ELF [tool-prefix]}"
prefix="${2:-riscv64-unknown-elf-}"
nm="${prefix}nm"
objdump="${prefix}objdump"
test -s "$elf"
for symbol in pvPortMalloc vPortFree prvInsertBlockIntoFreeList ucHeap \
heap4_reset_checkpoint heap4_alloc_a_checkpoint \
heap4_alloc_b_checkpoint heap4_alloc_c_checkpoint \
heap4_free_a_checkpoint heap4_fragmented_checkpoint \
heap4_coalesced_checkpoint task03_debug_checkpoint \
g_initial_stats g_after_allocations_stats g_fragmented_stats \
g_final_stats g_rv32_layout_expected g_task03_pass; do
"$nm" -a "$elf" | grep -E "[[:space:]]${symbol}$" >/dev/null
done
"$objdump" -d "$elf" | grep -E 'addi[[:space:]]+sp,sp,-' >/dev/null
printf 'PASS Task 3 links upstream heap_4 and eight observable checkpoints: %s\n' "$elf"
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
make -C "$root" PROFILE=observe host >/dev/null
check_output() {
local program="$1" expected="$2" actual
actual="$($program)"
if [[ "$actual" != "$expected" ]]; then
printf 'FAIL %s\nexpected: %s\nactual: %s\n' "$program" "$expected" "$actual" >&2
return 1
fi
printf 'PASS %s: %s\n' "$(basename -- "$(dirname -- "$program")")" "$actual"
}
check_output "$root/host-build/task01_first_fit_split/prog" \
'header=16 wanted=32 remainder=224 payload=16 aligned=1 pass=1'
check_output "$root/host-build/task02_address_order_coalesce/prog" \
'before_nodes=2 before_total=96 before_largest=48 after_nodes=1 after_total=144 after_largest=144 pass=1'
check_output "$root/host-build/task03_freertos_heap4/prog" \
'initial=4080 after_a=4040 after_b=3984 after_c=3952 free_a=3992 fragmented=4024 final=4080 min=3952 consumed=40/56/32 aligned=1 oom=1 hooks=1 asserts=0 pass=1'
+364
View File
@@ -0,0 +1,364 @@
#!/usr/bin/env bash
set -euo pipefail
action="${1:?action is required}"
root="${STEM_REPO:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)}"
profile="${STEM_PROFILE:-native-amd64}"
target="${STEM_TARGET:-native}"
selector="${STEM_TASK:-task3}"
state="${STEM_STATE_DIR:-$root/.stem/instances/local}"
tasks=(
task01_first_fit_split
task02_address_order_coalesce
task03_freertos_heap4
)
resolve_task() {
local value="${1,,}" digits number stem
for stem in "${tasks[@]}"; do
if [[ "$value" == "$stem" ]]; then
printf '%s\n' "$stem"
return
fi
done
if [[ "$value" =~ ^(task|t)?[-_]?0*([1-3])($|[-_].*) ]]; then
digits="${BASH_REMATCH[2]}"
number=$((10#$digits))
printf '%s\n' "${tasks[number - 1]}"
return
fi
printf 'Unknown task selector: %s\n' "$1" >&2
exit 2
}
task_stem="$(resolve_task "$selector")"
task_digits="${task_stem#task}"
task_digits="${task_digits%%_*}"
task_number=$((10#$task_digits))
task_key="$(printf 'task%02d' "$task_number")"
source_file="$root/src/tasks/$task_stem.c"
artifact_dir="$root/.stem/artifacts/$profile/$target/$task_key"
mkdir -p "$state" "$artifact_dir"
freertos_kernel_path() {
if [[ -n "${FREERTOS_KERNEL_PATH:-}" ]]; then
printf '%s\n' "$FREERTOS_KERNEL_PATH"
elif [[ -f /opt/FreeRTOS-Kernel/portable/MemMang/heap_4.c ]]; then
printf '%s\n' /opt/FreeRTOS-Kernel
else
printf '%s\n' "$root/../lab-rv32i-freertos-task-stack-vector/vendor/FreeRTOS-Kernel"
fi
}
verify_kernel() {
local kernel="$1"
test -f "$kernel/portable/MemMang/heap_4.c"
grep -F 'FreeRTOS Kernel V11.3.0' "$kernel/include/FreeRTOS.h" >/dev/null
}
freertos_rp2350_path() {
if [[ -n "${FREERTOS_RP2350_PATH:-}" ]]; then
printf '%s\n' "$FREERTOS_RP2350_PATH"
else
printf '%s\n' /opt/FreeRTOS-RP2350
fi
}
verify_rp2350_kernel() {
local kernel="$1"
test -f "$kernel/portable/ThirdParty/GCC/RP2350_RISC-V/portASM.S"
grep -F '<DEVELOPMENT BRANCH>' "$kernel/include/FreeRTOS.h" >/dev/null
}
copy_artifact() {
local path="$1"
[[ -f "$path" ]] && cp -f "$path" "$artifact_dir/"
}
build_native() {
local mode="${1:-normal}" kernel build_dir output cc="${CC:-cc}"
local flags=(-std=c11 -O0 -g3 -fno-omit-frame-pointer -fno-inline
-fno-optimize-sibling-calls -Wall -Wextra -Wpedantic
-DHOST_PRINTF=1 -I"$root/src/tasks")
build_dir="$state/build/native/$task_stem"
output="$build_dir/prog"
if [[ "$mode" == sanitizer ]]; then
flags+=(-fsanitize=address,undefined)
fi
mkdir -p "$build_dir"
if [[ "$task_number" -eq 3 ]]; then
kernel="$(freertos_kernel_path)"
verify_kernel "$kernel"
flags+=(-DHEAP4_HOST_ADAPTER=1 -I"$root/include/host-shim")
"$cc" "${flags[@]}" "$source_file" \
"$kernel/portable/MemMang/heap_4.c" -o "$output"
else
"$cc" "${flags[@]}" "$source_file" -o "$output"
fi
objdump -dS "$output" > "$build_dir/prog.lst"
cp -f "$output" "$artifact_dir/prog"
copy_artifact "$build_dir/prog.lst"
printf '%s\n' "$output"
}
rv_env_root() {
if [[ -n "${RV_ENV_ROOT:-}" ]]; then
printf '%s\n' "$RV_ENV_ROOT"
elif [[ -f "$root/vendor/Hazard3/test/sim/common/init.S" && \
-f "$root/vendor/lab-runtime/memops.c" ]]; then
printf '%s\n' "$root"
elif [[ -d /opt/rv-env ]]; then
printf '%s\n' /opt/rv-env
else
printf '%s\n' /opt/stem
fi
}
build_hazard3() {
local env_root kernel build_dir elf
env_root="$(rv_env_root)"
kernel="$(freertos_kernel_path)"
verify_kernel "$kernel"
build_dir="$state/build/hazard3"
make -B -C "$root" "task$task_number" \
"BUILD_ROOT=$build_dir" "RV_ENV_ROOT=$env_root" \
"FREERTOS_KERNEL_PATH=$kernel" PROFILE=observe >&2 || return
elf="$build_dir/$task_stem/prog.elf"
test -s "$elf"
if [[ "$task_number" -eq 3 ]]; then
"$root/tests/check_task03_elf.sh" "$elf" >&2
fi
copy_artifact "$elf"
copy_artifact "${elf%.elf}.bin"
copy_artifact "${elf%.elf}.lst"
copy_artifact "${elf%.elf}.map"
printf '%s\n' "$elf"
}
hazard3_tb() {
local env_root tb
env_root="$(rv_env_root)"
tb="$env_root/vendor/Hazard3/test/sim/tb_verilator/tb"
if [[ ! -x "$tb" ]]; then
make -C "$(dirname -- "$tb")" tb >&2
fi
printf '%s\n' "$tb"
}
build_rp2350() {
local binary_type="${1:-no_flash}" build_kind build_dir elf kernel rp_kernel
local cmake_args
case "$binary_type" in
no_flash) build_kind=ram ;;
default) build_kind=flash ;;
*) printf 'Unsupported RP2350 binary type: %s\n' "$binary_type" >&2; return 2 ;;
esac
kernel="$(freertos_kernel_path)"
verify_kernel "$kernel"
rp_kernel="$(freertos_rp2350_path)"
verify_rp2350_kernel "$rp_kernel"
build_dir="$state/build/rp2350-rv-$build_kind"
cmake_args=(
-S "$root" -B "$build_dir"
-DPICO_PLATFORM=rp2350-riscv
-DPICO_BOARD="${PICO_BOARD:-pico2_w}"
-DPICO_GCC_TRIPLE=riscv32-pico-elf
-DHEAP4_UPSTREAM_ROOT="$kernel"
-DFREERTOS_RP2350_PATH="$rp_kernel"
-DHEAP4_PROFILE=observe
-DHEAP4_BINARY_TYPE="$binary_type"
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
)
if command -v ninja >/dev/null 2>&1; then
cmake_args+=(-G Ninja)
else
cmake_args+=(-G "Unix Makefiles")
fi
if [[ -d /opt/picotool/picotool ]]; then
cmake_args+=(-Dpicotool_DIR=/opt/picotool/picotool)
fi
cmake "${cmake_args[@]}" >&2
cmake --build "$build_dir" --target "$task_stem" >&2
elf="$build_dir/$task_stem.elf"
test -s "$elf"
riscv64-unknown-elf-objdump -dS -M no-aliases,numeric "$elf" \
> "$build_dir/$task_stem.lst"
copy_artifact "$elf"
copy_artifact "$build_dir/$task_stem.bin"
copy_artifact "$build_dir/$task_stem.hex"
copy_artifact "$build_dir/$task_stem.uf2"
copy_artifact "$build_dir/$task_stem.dis"
copy_artifact "$build_dir/$task_stem.lst"
copy_artifact "$build_dir/$task_stem.elf.map"
printf '%s\n' "$elf"
}
openocd_target="${STEM_OPENOCD_TARGET:-target/rp2350.cfg}"
gdb_port="${GDB_PORT:-3333}"
gdb_target="${GDB_TARGET:-127.0.0.1:$gdb_port}"
openocd_pid=""
stop_owned_openocd() {
if [[ -n "$openocd_pid" ]]; then
kill "$openocd_pid" 2>/dev/null || true
wait "$openocd_pid" 2>/dev/null || true
fi
}
start_openocd() {
local log="$state/openocd-rp2350.log" attempt
if nc -z 127.0.0.1 "$gdb_port" 2>/dev/null; then
return
fi
openocd -f interface/cmsis-dap.cfg \
-c "adapter speed ${STEM_ADAPTER_KHZ:-5000}" \
-c 'set USE_CORE { rv0 }' -c 'set USE_SMP 0' \
-c "set FLASHSIZE ${STEM_RP2350_FLASH_SIZE:-0x400000}" \
-f "$openocd_target" >"$log" 2>&1 &
openocd_pid=$!
for attempt in $(seq 1 80); do
if nc -z 127.0.0.1 "$gdb_port" 2>/dev/null; then
return
fi
if ! kill -0 "$openocd_pid" 2>/dev/null; then
sed -n '1,240p' "$log" >&2
return 1
fi
sleep 0.1
done
printf 'OpenOCD did not open GDB port %s\n' "$gdb_port" >&2
return 1
}
flash_rp2350() {
local elf="$1" log="$state/openocd-rp2350-flash.log"
if nc -z 127.0.0.1 "$gdb_port" 2>/dev/null; then
printf 'GDB port %s is busy; stop the current debug session before deploy.\n' "$gdb_port" >&2
return 1
fi
if ! openocd -f interface/cmsis-dap.cfg \
-c "adapter speed ${STEM_ADAPTER_KHZ:-5000}" \
-c 'set USE_CORE { cm0 rv0 }' -c 'set USE_SMP 0' \
-c "set FLASHSIZE ${STEM_RP2350_FLASH_SIZE:-0x400000}" \
-f "$openocd_target" -c init \
-c 'if {[target_present rp2350.rv0]} { targets rp2350.rv0 } else { targets rp2350.cm0 }' \
-c 'reset halt' -c "flash write_image erase $elf" \
-c "verify_image $elf" -c 'reset run' -c shutdown >"$log" 2>&1; then
sed -n '1,260p' "$log" >&2
return 1
fi
grep -E 'wrote [0-9]+ bytes|verified [0-9]+ bytes' "$log" || true
printf 'rp2350-flash-ok image=%s verify=passed architecture=RISC-V\n' "$elf"
}
hardware_test_task03() {
local elf="$1" output
start_openocd
trap stop_owned_openocd EXIT
output="$(gdb-multiarch -nx -q -batch "$elf" \
-ex 'set architecture riscv:rv32' \
-ex "target extended-remote $gdb_target" \
-ex 'monitor reset halt' \
-ex 'hbreak heap4_fragmented_checkpoint' \
-ex 'hbreak task03_debug_checkpoint' \
-ex continue \
-ex 'printf "fragmented blocks=%lu free=%lu largest=%lu\n", g_fragmented_stats.xNumberOfFreeBlocks, g_fragmented_stats.xAvailableHeapSpaceInBytes, g_fragmented_stats.xSizeOfLargestFreeBlockInBytes' \
-ex continue \
-ex 'printf "task03 initial=%lu after=%lu final=%lu min=%lu aligned=%d oom=%d hooks=%d asserts=%d pass=%d\n", g_initial_free, g_after_allocations, g_final_free, g_minimum_ever_free, g_allocations_aligned, g_oom_is_null, g_malloc_failed_hooks, g_assert_failures, g_task03_pass' \
-ex 'printf "frame sp=0x%08lx fp=0x%08lx pc=0x%08lx align=%lu\n", (unsigned long)$sp, (unsigned long)$s0, (unsigned long)$pc, ((unsigned long)$sp & 15)' \
-ex 'x/96bx ucHeap' \
-ex 'delete breakpoints' -ex 'monitor halt' -ex disconnect)"
printf '%s\n' "$output"
grep -E '^fragmented blocks=2 ' <<<"$output" >/dev/null
grep -E '^task03 .* aligned=1 oom=1 hooks=1 asserts=0 pass=1$' <<<"$output" >/dev/null
grep -E '^frame .* align=0$' <<<"$output" >/dev/null
printf 'rp2350-hardware-ok task=task03 heap_4=upstream load=flash\n'
}
configure_debug_view() {
export STEM_DEBUG_WINDOW_NAME=heap4
case "$task_number" in
1)
export STEM_DEBUG_INITIAL_BREAK=task01_debug_checkpoint
export STEM_DEBUG_MEMORY_EXPR='&g_split_arena' STEM_DEBUG_MEMORY_LENGTH=288
;;
2)
export STEM_DEBUG_INITIAL_BREAK=task02_fragmented_checkpoint
export STEM_DEBUG_MEMORY_EXPR='&g_coalesce_arena' STEM_DEBUG_MEMORY_LENGTH=160
;;
3)
export STEM_DEBUG_INITIAL_BREAK=heap4_reset_checkpoint
export STEM_DEBUG_MEMORY_EXPR='ucHeap' STEM_DEBUG_MEMORY_LENGTH=160
;;
esac
}
case "$profile:$target:$action" in
native-amd64:native:build)
build_native normal >/dev/null
;;
native-amd64:native:test)
output="$(build_native sanitizer)"
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 "$output"
;;
native-amd64:native:run)
output="$(build_native normal)"
exec "$output"
;;
native-amd64:native:debug)
output="$(build_native normal)"
export RV_REPO="$root" RV_EDITOR="${STEM_EDITOR:-nvim}"
export RV_HOST_ELF="$output" RV_HOST_SRC="$source_file"
export RV_HOST_LST="$(dirname -- "$output")/prog.lst"
exec /opt/stem/scripts/host-entry.sh debug "task$task_number"
;;
hazard3-sim:hazard3-baremetal:build)
build_hazard3 >/dev/null
;;
hazard3-sim:hazard3-baremetal:test|hazard3-sim:hazard3-baremetal:run)
elf="$(build_hazard3)"
tb="$(hazard3_tb)"
exec "$tb" --bin "${elf%.elf}.bin" --cpuret
;;
hazard3-sim:hazard3-baremetal:debug)
elf="$(build_hazard3)"
configure_debug_view
export STEM_DEBUG_BACKEND=hazard3 STEM_DEBUG_ELF="$elf"
export STEM_DEBUG_BIN="${elf%.elf}.bin" STEM_DEBUG_SRC="$source_file"
export STEM_DEBUG_LST="${elf%.elf}.lst"
exec /opt/stem/scripts/card-debug-ui.sh
;;
rp2350:rp2350-rv:build)
build_rp2350 >/dev/null
;;
rp2350:rp2350-rv:test)
elf="$(build_rp2350)"
riscv64-unknown-elf-readelf -h "$elf" | grep -F 'Machine:' | grep -F 'RISC-V'
if [[ "$task_number" -eq 3 ]]; then
"$root/tests/check_task03_elf.sh" "$elf"
fi
printf 'rp2350-build-ok task=%s binary_type=no_flash\n' "$task_key"
;;
rp2350:rp2350-rv:deploy)
elf="$(build_rp2350 default)"
flash_rp2350 "$elf"
if [[ "$task_number" -eq 3 ]]; then
hardware_test_task03 "$elf"
else
printf 'rp2350-deploy-ok task=%s load=flash persistent=1\n' "$task_key"
fi
;;
rp2350:rp2350-rv:debug)
elf="$(build_rp2350)"
configure_debug_view
export STEM_DEBUG_BACKEND=rp2350 STEM_DEBUG_ELF="$elf"
export STEM_DEBUG_SRC="$source_file" STEM_DEBUG_LST="${elf%.elf}.lst"
exec /opt/stem/scripts/card-debug-ui.sh
;;
*)
printf 'Unsupported profile/target/action: %s/%s/%s\n' "$profile" "$target" "$action" >&2
exit 2
;;
esac
+269
View File
@@ -0,0 +1,269 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
`default_nettype none
module hazard3_alu #(
`include "hazard3_config.vh"
,
`include "hazard3_width_const.vh"
) (
input wire [W_ALUOP-1:0] aluop,
input wire [6:0] funct7_32b,
input wire [2:0] funct3_32b,
input wire [W_DATA-1:0] op_a,
input wire [W_DATA-1:0] op_b,
output reg [W_DATA-1:0] result,
output wire cmp
);
`include "hazard3_ops.vh"
// ----------------------------------------------------------------------------
// Fiddle around with add/sub, comparisons etc (all related).
wire sub = !(aluop == ALUOP_ADD || (|EXTENSION_ZBA && aluop == ALUOP_SHXADD));
wire inv_op_b = sub && !(
aluop == ALUOP_AND || aluop == ALUOP_OR || aluop == ALUOP_XOR || aluop == ALUOP_RS2
);
wire [W_DATA-1:0] op_a_shifted =
|EXTENSION_ZBA && aluop == ALUOP_SHXADD ? (
!funct3_32b[2] ? op_a << 1 :
!funct3_32b[1] ? op_a << 2 : op_a << 3
) : op_a;
wire [W_DATA-1:0] op_b_inv = op_b ^ {W_DATA{inv_op_b}};
wire [W_DATA-1:0] sum = op_a_shifted + op_b_inv + {{W_DATA-1{1'b0}}, sub};
wire [W_DATA-1:0] op_xor = op_a ^ op_b;
wire cmp_is_unsigned = aluop == ALUOP_LTU ||
|EXTENSION_ZBB && aluop == ALUOP_MAXU ||
|EXTENSION_ZBB && aluop == ALUOP_MINU;
wire lt = op_a[W_DATA-1] == op_b[W_DATA-1] ? sum[W_DATA-1] :
cmp_is_unsigned ? op_b[W_DATA-1] : op_a[W_DATA-1] ;
assign cmp = aluop == ALUOP_SUB ? |op_xor : lt;
// ----------------------------------------------------------------------------
// Separate units for shift, ctz etc
wire [W_DATA-1:0] shift_dout;
wire shift_right_nleft =
aluop == ALUOP_SRL ||
aluop == ALUOP_SRA ||
(|EXTENSION_ZBB && aluop == ALUOP_ROR ) ||
(|EXTENSION_ZBS && aluop == ALUOP_BEXT ) ||
(|EXTENSION_XH3BEXTM && aluop == ALUOP_BEXTM);
wire shift_arith = aluop == ALUOP_SRA;
wire shift_rotate = |EXTENSION_ZBB & (aluop == ALUOP_ROR || aluop == ALUOP_ROL);
hazard3_shift_barrel #(
`include "hazard3_config_inst.vh"
) shifter (
.din (op_a),
.shamt (op_b[4:0]),
.right_nleft (shift_right_nleft),
.rotate (shift_rotate),
.arith (shift_arith),
.dout (shift_dout)
);
reg [W_DATA-1:0] op_a_rev;
always @ (*) begin: rev_op_a
integer i;
for (i = 0; i < W_DATA; i = i + 1) begin
op_a_rev[i] = op_a[W_DATA - 1 - i];
end
end
// "leading" means starting at MSB. This is an LSB-first priority encoder, so
// "leading" is reversed and "trailing" is not.
wire [W_DATA-1:0] ctz_search_mask = aluop == ALUOP_CLZ ? op_a_rev : op_a;
wire [W_SHAMT:0] ctz_clz;
hazard3_priority_encode #(
.W_REQ (W_DATA),
.HIGHEST_WINS (0)
) ctz_priority_encode (
.req (ctz_search_mask),
.gnt (ctz_clz[W_SHAMT-1:0])
);
// Special case: all-zeroes returns XLEN
assign ctz_clz[W_SHAMT] = ~|op_a;
reg [W_SHAMT:0] cpop;
always @ (*) begin: cpop_count
integer i;
cpop = {W_SHAMT+1{1'b0}};
for (i = 0; i < W_DATA; i = i + 1) begin
cpop = cpop + {{W_SHAMT{1'b0}}, op_a[i]};
end
end
reg [2*W_DATA-1:0] clmul64;
always @ (*) begin: clmul_mul
integer i;
clmul64 = {2*W_DATA{1'b0}};
for (i = 0; i < W_DATA; i = i + 1) begin
clmul64 = clmul64 ^ (({{W_DATA{1'b0}}, op_a} << i) & {2*W_DATA{op_b[i]}});
end
end
// funct3: 1=clmul, 2=clmulr, 3=clmulh, never 0.
wire [W_DATA-1:0] clmul =
!funct3_32b[1] ? clmul64[31: 0] :
!funct3_32b[0] ? clmul64[62:31] : clmul64[63:32];
reg [W_DATA-1:0] zip;
reg [W_DATA-1:0] unzip;
always @ (*) begin: do_zip_unzip
integer i;
for (i = 0; i < W_DATA; i = i + 1) begin
zip[i] = op_a[{i[0], i[4:1]}]; // Alternate high/low halves
unzip[i] = op_a[{i[3:0], i[4]}]; // All even then all odd
end
end
reg [W_DATA-1:0] xperm8;
always @ (*) begin: do_xperm8
integer i;
for (i = 0; i < W_DATA; i = i + 8) begin
if (|op_b[i + 2 +: 6]) begin
xperm8[i +: 8] = 8'h00;
end else begin
xperm8[i +: 8] = op_a[8 * op_b[i +: 2] +: 8];
end
end
end
reg [W_DATA-1:0] xperm4;
always @ (*) begin: do_xperm4
integer i;
for (i = 0; i < W_DATA; i = i + 4) begin
if (op_b[i + 3]) begin
xperm4[i +: 4] = 4'h0;
end else begin
xperm4[i +: 4] = op_a[4 * op_b[i +: 3] +: 4];
end
end
end
// ----------------------------------------------------------------------------
// Output mux, with simple operations inline
// iCE40: We can implement all bitwise ops with 1 LUT4/bit total, since each
// result bit uses only two operand bits. Much better than feeding each into
// main mux tree. Doesn't matter for big-LUT FPGAs or for implementations with
// bitmanip extensions enabled.
reg [W_DATA-1:0] bitwise;
always @ (*) begin: bitwise_ops
case (aluop[1:0])
ALUOP_AND[1:0]: bitwise = op_a & op_b_inv;
ALUOP_OR [1:0]: bitwise = op_a | op_b_inv;
ALUOP_XOR[1:0]: bitwise = op_a ^ op_b_inv;
ALUOP_RS2[1:0]: bitwise = op_b_inv;
endcase
end
wire [W_DATA-1:0] zbs_mask = {{W_DATA-1{1'b0}}, 1'b1} << op_b[W_SHAMT-1:0];
always @ (*) begin
casez ({|EXTENSION_A, |EXTENSION_ZBA, |EXTENSION_ZBB, |EXTENSION_ZBC,
|EXTENSION_ZBS, |EXTENSION_ZBKB, |EXTENSION_ZBKX, |EXTENSION_XH3BEXTM, aluop})
// Base ISA
{8'bzzzzzzzz, ALUOP_ADD }: result = sum;
{8'bzzzzzzzz, ALUOP_SUB }: result = sum;
{8'bzzzzzzzz, ALUOP_LT }: result = {{W_DATA-1{1'b0}}, lt};
{8'bzzzzzzzz, ALUOP_LTU }: result = {{W_DATA-1{1'b0}}, lt};
{8'bzzzzzzzz, ALUOP_SRL }: result = shift_dout;
{8'bzzzzzzzz, ALUOP_SRA }: result = shift_dout;
{8'bzzzzzzzz, ALUOP_SLL }: result = shift_dout;
// A or Zbb (written this way to avoid case overlap)
{8'b1zzzzzzz, ALUOP_MAX },
{8'b0z1zzzzz, ALUOP_MAX }: result = lt ? op_b : op_a;
{8'b1zzzzzzz, ALUOP_MIN },
{8'b0z1zzzzz, ALUOP_MIN }: result = lt ? op_a : op_b;
{8'b1zzzzzzz, ALUOP_MAXU },
{8'b0z1zzzzz, ALUOP_MAXU }: result = lt ? op_b : op_a;
{8'b1zzzzzzz, ALUOP_MINU },
{8'b0z1zzzzz, ALUOP_MINU }: result = lt ? op_a : op_b;
// Zba
{8'bz1zzzzzz, ALUOP_SHXADD }: result = sum;
// Zbb
{8'bzz1zzzzz, ALUOP_ANDN }: result = bitwise;
{8'bzz1zzzzz, ALUOP_ORN }: result = bitwise;
{8'bzz1zzzzz, ALUOP_XNOR }: result = bitwise;
{8'bzz1zzzzz, ALUOP_CLZ }: result = {{W_DATA-W_SHAMT-1{1'b0}}, ctz_clz};
{8'bzz1zzzzz, ALUOP_CTZ }: result = {{W_DATA-W_SHAMT-1{1'b0}}, ctz_clz};
{8'bzz1zzzzz, ALUOP_CPOP }: result = {{W_DATA-W_SHAMT-1{1'b0}}, cpop};
{8'bzz1zzzzz, ALUOP_SEXT_B }: result = {{W_DATA-8{op_a[7]}}, op_a[7:0]};
{8'bzz1zzzzz, ALUOP_SEXT_H }: result = {{W_DATA-16{op_a[15]}}, op_a[15:0]};
{8'bzz1zzzzz, ALUOP_ZEXT_H }: result = {{W_DATA-16{1'b0}}, op_a[15:0]};
{8'bzz1zzzzz, ALUOP_ORC_B }: result = {{8{|op_a[31:24]}}, {8{|op_a[23:16]}}, {8{|op_a[15:8]}}, {8{|op_a[7:0]}}};
{8'bzz1zzzzz, ALUOP_REV8 }: result = {op_a[7:0], op_a[15:8], op_a[23:16], op_a[31:24]};
{8'bzz1zzzzz, ALUOP_ROL }: result = shift_dout;
{8'bzz1zzzzz, ALUOP_ROR }: result = shift_dout;
// Zbc
{8'bzzz1zzzz, ALUOP_CLMUL }: result = clmul;
// Zbs
{8'bzzzz1zzz, ALUOP_BCLR }: result = op_a & ~zbs_mask;
{8'bzzzz1zzz, ALUOP_BSET }: result = op_a | zbs_mask;
{8'bzzzz1zzz, ALUOP_BINV }: result = op_a ^ zbs_mask;
{8'bzzzz1zzz, ALUOP_BEXT }: result = {{W_DATA-1{1'b0}}, shift_dout[0]};
// Zbkb
{8'bzzzzz1zz, ALUOP_PACK }: result = {op_b[15:0], op_a[15:0]};
{8'bzzzzz1zz, ALUOP_PACKH }: result = {{W_DATA-16{1'b0}}, op_b[7:0], op_a[7:0]};
{8'bzzzzz1zz, ALUOP_BREV8 }: result = {op_a_rev[7:0], op_a_rev[15:8], op_a_rev[23:16], op_a_rev[31:24]};
{8'bzzzzz1zz, ALUOP_UNZIP }: result = unzip;
{8'bzzzzz1zz, ALUOP_ZIP }: result = zip;
// Zbkx
{8'bzzzzzz1z, ALUOP_XPERM }: result = funct3_32b[2] ? xperm8 : xperm4;
// Xh3bextm
{8'bzzzzzzz1, ALUOP_BEXTM }: result = shift_dout & {24'h0, {~(8'hfe << funct7_32b[3:1])}};
default: result = bitwise;
endcase
end
// ----------------------------------------------------------------------------
// Properties for base-ISA instructions
`ifdef HAZARD3_ASSERTIONS
`ifndef RISCV_FORMAL
// Really we're just interested in the shifts and comparisons, as these are
// the nontrivial ones. However, easier to test everything!
wire clk;
always @ (posedge clk) begin
case(aluop)
default: begin end
ALUOP_ADD: assert(result == op_a + op_b);
ALUOP_SUB: assert(result == op_a - op_b);
ALUOP_LT: assert(result == $signed(op_a) < $signed(op_b));
ALUOP_LTU: assert(result == op_a < op_b);
ALUOP_AND: assert(result == (op_a & op_b));
ALUOP_OR: assert(result == (op_a | op_b));
ALUOP_XOR: assert(result == (op_a ^ op_b));
ALUOP_SRL: assert(result == op_a >> op_b[4:0]);
ALUOP_SRA: assert($signed(result) == $signed(op_a) >>> $signed(op_b[4:0]));
ALUOP_SLL: assert(result == op_a << op_b[4:0]);
endcase
end
`endif
`endif
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+52
View File
@@ -0,0 +1,52 @@
/*****************************************************************************\
| Copyright (C) 2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
`default_nettype none
// The branch decision path through the ALU is slow because:
//
// - Sees immediates and PC on its inputs, as well as regs
// - Add/sub rather than just add (with complex decode of the sub condition)
// - 2 extra mux layers in front of adder if Zba extension is enabled
//
// So there is sometimes timing benefit to a dedicated branch comparator.
module hazard3_branchcmp #(
`include "hazard3_config.vh"
,
`include "hazard3_width_const.vh"
) (
input wire [31:0] cir,
input wire [W_DATA-1:0] op_a,
input wire [W_DATA-1:0] op_b,
output wire cmp
);
`include "hazard3_ops.vh"
wire [W_DATA-1:0] diff = op_a - op_b;
// funct3 instruction
// ------------------
// 000 BEQ
// 001 BNE
// 100 BLT
// 101 BGE
// 110 BLTU
// 111 BGEU
wire cmp_is_unsigned = cir[13];
wire lt = op_a[W_DATA-1] == op_b[W_DATA-1] ? diff[W_DATA-1] :
cmp_is_unsigned ? op_b[W_DATA-1] :
op_a[W_DATA-1] ;
assign cmp = cir[14] ? lt : op_a != op_b;
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+162
View File
@@ -0,0 +1,162 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// MUL-only (cfg: MUL_FAST) and MUL/MULH/MULHU/MULHSU (cfg: MUL_FAST &&
// MULH_FAST) are handled by different circuits. In either case it's a simple
// behavioural multiply, and we rely on inference to get good performance on
// FPGA.
`default_nettype none
module hazard3_mul_fast #(
`include "hazard3_config.vh"
,
`include "hazard3_width_const.vh"
) (
input wire clk,
input wire rst_n,
input wire [W_MULOP-1:0] op,
input wire op_vld,
input wire [W_DATA-1:0] op_a,
input wire [W_DATA-1:0] op_b,
output wire [W_DATA-1:0] result,
output reg result_vld
);
`include "hazard3_ops.vh"
localparam XLEN = W_DATA;
//synthesis translate_off
generate if (MULH_FAST && !MUL_FAST) begin: err_require_mul_fast_for_mulh
initial $fatal("%m: MULH_FAST requires that MUL_FAST is also set.");
end endgenerate
generate if (MUL_FASTER && !MUL_FAST) begin: err_require_mul_fast_for_faster
initial $fatal("%m: MUL_FASTER requires that MUL_FAST is also set.");
end endgenerate
//synthesis translate_on
// Latency of 1:
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
result_vld <= 1'b0;
end else begin
result_vld <= op_vld;
end
end
// ----------------------------------------------------------------------------
// Fast MUL only
generate
if (!MULH_FAST) begin: mul_only
// This pipestage is folded into the front of the DSP tiles on UP5k. Note the
// intention is to register the bypassed core regs at the end of X (since
// bypass is quite slow), then perform multiply combinatorially in stage M,
// and mux into MW result register.
reg [XLEN-1:0] op_a_r;
reg [XLEN-1:0] op_b_r;
if (MUL_FASTER) begin: op_passthrough
always @ (*) begin
op_a_r = op_a;
op_b_r = op_b;
end
end else begin: op_register
always @ (posedge clk) begin
if (op_vld) begin
op_a_r <= op_a;
op_b_r <= op_b;
end
end
end
// This should be inferred as 3 DSP tiles on UP5k:
//
// 1. Register then multiply a[15: 0] and b[15: 0]
// 2. Register then multiply a[31:16] and b[15: 0], then directly add output of 1
// 3. Register then multiply a[15: 0] and b[31:16], then directly add output of 2
//
// So there is quite a long path (1x 16-bit multiply, then 2x 16-bit add). On
// other platforms you may just end up with a pile of gates.
`ifndef RISCV_FORMAL_ALTOPS
assign result = op_a_r * op_b_r;
`else
// riscv-formal can use a simpler function, since it's just confirming the
// result is correctly hooked up.
assign result = result_vld ? (op_a_r + op_b_r) ^ 32'h5876063e : 32'hdeadbeef;
`endif
// ----------------------------------------------------------------------------
// Fast MUL/MULH/MULHU/MULHSU
end else begin: mul_and_mulh
reg [XLEN-1:0] op_a_r;
reg [XLEN-1:0] op_b_r;
reg [W_MULOP-1:0] op_r;
if (MUL_FASTER) begin: op_passthrough
always @ (*) begin
op_a_r = op_a;
op_b_r = op_b;
op_r = op;
end
end else begin: op_register
always @ (posedge clk) begin
if (op_vld) begin
op_a_r <= op_a;
op_b_r <= op_b;
op_r <= op;
end
end
end
wire op_a_signed = op_r == M_OP_MULH || op_r == M_OP_MULHSU;
wire op_b_signed = op_r == M_OP_MULH;
wire [2*XLEN-1:0] op_a_sext = {
{XLEN{op_a_r[XLEN - 1] && op_a_signed}},
op_a_r
};
wire [2*XLEN-1:0] op_b_sext = {
{XLEN{op_b_r[XLEN - 1] && op_b_signed}},
op_b_r
};
wire [2*XLEN-1:0] result_full = op_a_sext * op_b_sext;
`ifndef RISCV_FORMAL_ALTOPS
assign result = op_r == M_OP_MUL ? result_full[0 +: XLEN] : result_full[XLEN +: XLEN];
`else
assign result =
op_r == M_OP_MULH ? (op_a_r + op_b_r) ^ 32'hf6583fb7 :
op_r == M_OP_MULHSU ? (op_a_r - op_b_r) ^ 32'hecfbe137 :
op_r == M_OP_MULHU ? (op_a_r + op_b_r) ^ 32'h949ce5e8 :
op_r == M_OP_MUL ? (op_a_r + op_b_r) ^ 32'h5876063e : 32'hdeadbeef;
`endif
end
endgenerate
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+294
View File
@@ -0,0 +1,294 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Combined multiply/divide/modulo circuit. All operations performed at 1 bit
// per clock; aiming for minimal resource usage on iCE40 FPGA. Optionally the
// circuit can be unrolled for slightly higher performance.
//
// When op_kill is high, the current calculation halts immediately. op_vld can
// be asserted on the same cycle, and the new calculation begins without
// delay, regardless of op_rdy. This may be used by the processor on e.g.
// mispredict or trap.
//
// The actual multiply/divide hardware is unsigned. We handle signedness at
// input/output.
`default_nettype none
module hazard3_muldiv_seq #(
`include "hazard3_config.vh"
,
`include "hazard3_width_const.vh"
) (
input wire clk,
input wire rst_n,
input wire [W_MULOP-1:0] op,
input wire op_vld,
output wire op_rdy,
input wire op_kill,
input wire [W_DATA-1:0] op_a,
input wire [W_DATA-1:0] op_b,
output wire [W_DATA-1:0] result_h, // mulh* or rem*
output wire [W_DATA-1:0] result_l, // mul or div*
output wire result_vld
);
`include "hazard3_ops.vh"
//synthesis translate_off
generate if (|(MULDIV_UNROLL & (MULDIV_UNROLL - 1)) || ~|MULDIV_UNROLL) begin: err_pow2
initial $fatal("%m: MULDIV_UNROLL must be a positive power of 2");
end endgenerate
//synthesis translate_on
localparam XLEN = W_DATA;
parameter W_CTR = $clog2(XLEN + 1);
// ----------------------------------------------------------------------------
// Operation decode, operand sign adjustment
// On the first cycle, op_a and op_b go straight through to the accumulator
// and the divisor/multiplicand register. They are then adjusted in-place
// on the next cycle. This allows the same circuits to be reused for sign
// adjustment before output (and helps input timing).
reg [W_MULOP-1:0] op_r;
reg [2*XLEN-1:0] accum;
reg [XLEN-1:0] op_b_r;
reg op_a_neg_r;
reg op_b_neg_r;
wire op_a_signed =
op_r == M_OP_MULH ||
op_r == M_OP_MULHSU ||
op_r == M_OP_DIV ||
op_r == M_OP_REM;
wire op_b_signed =
op_r == M_OP_MULH ||
op_r == M_OP_DIV ||
op_r == M_OP_REM;
wire op_a_neg = op_a_signed && accum[XLEN-1];
wire op_b_neg = op_b_signed && op_b_r[XLEN-1];
// Non-divide parts of the circuit should be constant-folded if all the MUL
// operations are handled by the fast multiplier
wire is_div = op_r[2] || (MUL_FAST && MULH_FAST);
// Controls for modifying sign of all/part of accumulator
wire accum_neg_l;
wire accum_inv_h;
wire accum_incr_h;
// ----------------------------------------------------------------------------
// Arithmetic circuit
// Combinatorials:
reg [2*XLEN-1:0] accum_next;
reg [2*XLEN-1:0] addend;
reg [2*XLEN-1:0] shift_tmp;
reg [2*XLEN-1:0] addsub_tmp;
reg neg_l_borrow;
always @ (*) begin: alu
integer i;
// Multiply/divide iteration layers
accum_next = accum;
addend = {2*XLEN{1'b0}};
addsub_tmp = {2*XLEN{1'b0}};
neg_l_borrow = 1'b0;
for (i = 0; i < MULDIV_UNROLL; i = i + 1) begin
addend = {is_div && |op_b_r, op_b_r, {XLEN-1{1'b0}}};
shift_tmp = is_div ? accum_next : accum_next >> 1;
addsub_tmp = shift_tmp + addend;
accum_next = (is_div ? !addsub_tmp[2 * XLEN - 1] : accum_next[0]) ?
addsub_tmp : shift_tmp;
if (is_div)
accum_next = {accum_next[2*XLEN-2:0], !addsub_tmp[2 * XLEN - 1]};
end
// Alternative path for negation of all/part of accumulator
if (accum_neg_l)
{neg_l_borrow, accum_next[XLEN-1:0]} = {~accum[XLEN-1:0]} + 1'b1;
if (accum_incr_h || accum_inv_h)
accum_next[XLEN +: XLEN] = (accum[XLEN +: XLEN] ^ {XLEN{accum_inv_h}})
+ {{XLEN-1{1'b0}}, accum_incr_h};
end
// ----------------------------------------------------------------------------
// Main state machine
reg sign_preadj_done;
reg [W_CTR-1:0] ctr;
reg sign_postadj_done;
reg sign_postadj_carry;
localparam CTR_TOP = XLEN[W_CTR-1:0];
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
ctr <= {W_CTR{1'b0}};
sign_preadj_done <= 1'b1;
sign_postadj_done <= 1'b1;
sign_postadj_carry <= 1'b0;
op_r <= {W_MULOP{1'b0}};
op_a_neg_r <= 1'b0;
op_b_neg_r <= 1'b0;
op_b_r <= {XLEN{1'b0}};
accum <= {XLEN*2{1'b0}};
end else if (op_kill || (op_vld && op_rdy)) begin
// Initialise circuit with operands + state
ctr <= op_vld ? CTR_TOP : {W_CTR{1'b0}};
sign_preadj_done <= !op_vld;
sign_postadj_done <= !op_vld;
sign_postadj_carry <= 1'b0;
op_r <= op;
op_b_r <= op_b;
accum <= {{XLEN{1'b0}}, op_a};
end else if (!sign_preadj_done) begin
// Pre-adjust sign if necessary, else perform first iteration immediately
op_a_neg_r <= op_a_neg;
op_b_neg_r <= op_b_neg;
sign_preadj_done <= 1'b1;
if (accum_neg_l || (op_b_neg ^ is_div)) begin
if (accum_neg_l)
accum[0 +: XLEN] <= accum_next[0 +: XLEN];
if (op_b_neg ^ is_div)
op_b_r <= -op_b_r;
end else begin
ctr <= ctr - MULDIV_UNROLL[W_CTR-1:0];
accum <= accum_next;
end
end else if (|ctr) begin
ctr <= ctr - MULDIV_UNROLL[W_CTR-1:0];
accum <= accum_next;
end else if (!sign_postadj_done || sign_postadj_carry) begin
sign_postadj_done <= 1'b1;
if (accum_inv_h || accum_incr_h)
accum[XLEN +: XLEN] <= accum_next[XLEN +: XLEN];
if (accum_neg_l) begin
accum[0 +: XLEN] <= accum_next[0 +: XLEN];
if (!is_div) begin
sign_postadj_carry <= neg_l_borrow;
sign_postadj_done <= !neg_l_borrow;
end
end
end
end
// ----------------------------------------------------------------------------
// Sign adjustment control
// Pre-adjustment: for any a, b we want |a|, |b|. Note that the magnitude of any
// 32-bit signed integer is representable by a 32-bit unsigned integer.
// Post-adjustment for division:
// We seek q, r to satisfy a = b * q + r, where a and b are given,
// and |r| < |b|. One way to do this is if
// sgn(r) = sgn(a)
// sgn(q) = sgn(a) ^ sgn(b)
// This has additional nice properties like
// -(a / b) = (-a) / b = a / (-b)
// Post-adjustment for multiplication:
// We have calculated the 2*XLEN result of |a| * |b|.
// Negate the entire accumulator if sgn(a) ^ sgn(b).
// This is done in two steps (to share div/mod circuit, and avoid 64-bit carry):
// - Negate lower half of accumulator, and invert upper half
// - Increment upper half if lower half carried
wire do_postadj = ~|{ctr, sign_postadj_done};
wire op_signs_differ = op_a_neg_r ^ op_b_neg_r;
assign accum_neg_l =
!sign_preadj_done && op_a_neg ||
do_postadj && !sign_postadj_carry && op_signs_differ && !(is_div && ~|op_b_r);
assign {accum_incr_h, accum_inv_h} =
do_postadj && is_div && op_a_neg_r ? 2'b11 :
do_postadj && !is_div && op_signs_differ && !sign_postadj_carry ? 2'b01 :
do_postadj && !is_div && op_signs_differ && sign_postadj_carry ? 2'b10 :
2'b00 ;
// ----------------------------------------------------------------------------
// Outputs
assign op_rdy = ~|{ctr, accum_neg_l, accum_incr_h, accum_inv_h};
assign result_vld = op_rdy;
`ifndef RISCV_FORMAL_ALTOPS
assign {result_h, result_l} = accum;
`else
// Provide arithmetically simpler alternative operations, to speed up formal checks
always assert(XLEN == 32);
reg [XLEN-1:0] fml_a_saved;
reg [XLEN-1:0] fml_b_saved;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
fml_a_saved <= {XLEN{1'b0}};
fml_b_saved <= {XLEN{1'b0}};
end else if (op_vld && op_rdy) begin
fml_a_saved <= op_a;
fml_b_saved <= op_b;
end
end
assign result_h =
op_r == M_OP_MULH ? (fml_a_saved + fml_b_saved) ^ 32'hf6583fb7 :
op_r == M_OP_MULHSU ? (fml_a_saved - fml_b_saved) ^ 32'hecfbe137 :
op_r == M_OP_MULHU ? (fml_a_saved + fml_b_saved) ^ 32'h949ce5e8 :
op_r == M_OP_REM ? (fml_a_saved - fml_b_saved) ^ 32'h8da68fa5 :
op_r == M_OP_REMU ? (fml_a_saved - fml_b_saved) ^ 32'h3138d0e1 : 32'hdeadbeef;
assign result_l =
op_r == M_OP_MUL ? (fml_a_saved + fml_b_saved) ^ 32'h5876063e :
op_r == M_OP_DIV ? (fml_a_saved - fml_b_saved) ^ 32'h7f8529ec :
op_r == M_OP_DIVU ? (fml_a_saved - fml_b_saved) ^ 32'h10e8fd70 : 32'hdeadbeef;
`endif
// ----------------------------------------------------------------------------
// Interface properties
`ifdef HAZARD3_ASSERTIONS
always @ (posedge clk) if (rst_n && $past(rst_n)) begin: properties
integer i;
reg alive;
if ($past(op_rdy && !op_vld))
assert(op_rdy);
if (result_vld && $past(result_vld) && !$past(op_kill))
assert($stable({result_h, result_l}));
// Kill will halt an in-progress operation, but a new operation may be
// asserted simultaneously with kill.
if ($past(op_kill))
assert(op_rdy == !$past(op_vld));
// We should be periodically ready (liveness property), unless new operations
// are forced in immediately, simultaneous with a kill, in which case there
// is no intermediate ready state.
alive = op_rdy || (op_kill && op_vld);
for (i = 1; i <= XLEN / MULDIV_UNROLL + 3; i = i + 1)
alive = alive || $past(op_rdy || (op_kill && op_vld), i);
assert(alive);
end
`endif
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+31
View File
@@ -0,0 +1,31 @@
/*****************************************************************************\
| Copyright (C) 2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// req: one-hot bitmap
// idx: index of the sole set bit in req
`default_nettype none
module hazard3_onehot_encode #(
parameter W_REQ = 16,
parameter W_GNT = $clog2(W_REQ) // do not modify
) (
input wire [W_REQ-1:0] req,
output reg [W_GNT-1:0] gnt
);
always @ (*) begin: encode
reg [W_GNT:0] i;
gnt = {W_GNT{1'b0}};
for (i = 0; i < W_REQ; i = i + 1) begin
gnt = gnt | ({W_GNT{req[i[W_GNT-1:0]]}} & i[W_GNT-1:0]);
end
end
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+33
View File
@@ -0,0 +1,33 @@
/*****************************************************************************\
| Copyright (C) 2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// req: bitmap
// idx: bitmap with all bits clear except the least- (HIGHEST_WINS=0) or
// most- (HIGHEST_WINS=1) significant set bit in req.
`default_nettype none
module hazard3_onehot_priority #(
parameter W_REQ = 16,
parameter HIGHEST_WINS = 0
) (
input wire [W_REQ-1:0] req,
output reg [W_REQ-1:0] gnt
);
always @ (*) begin: select
integer i;
for (i = 0; i < W_REQ; i = i + 1) begin
gnt[i] = req[i] && ~|(req & (
HIGHEST_WINS ? ~({W_REQ{1'b1}} >> (W_REQ - 1 - i)) : ~({W_REQ{1'b1}} << i)
));
end
end
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
@@ -0,0 +1,77 @@
/*****************************************************************************\
| Copyright (C) 2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// req: bitmap of requests
// priority: packed array of dynamic priority level of each request
// gnt: one-hot bitmap with the highest-priority request.
`default_nettype none
module hazard3_onehot_priority_dynamic #(
parameter W_REQ = 8,
parameter N_PRIORITIES = 2,
parameter PRIORITY_HIGHEST_WINS = 1, // If 1, numerically highest level has greatest priority.
// Otherwise, numerically lowest wins.
parameter TIEBREAK_HIGHEST_WINS = 0, // If 1, highest-numbered request at the highest priority
// level wins the tiebreak. Otherwise, lowest-numbered.
// Do not modify:
parameter W_PRIORITY = $clog2(N_PRIORITIES)
) (
input wire [W_REQ*W_PRIORITY-1:0] pri,
input wire [W_REQ-1:0] req,
output wire [W_REQ-1:0] gnt
);
// 1. Stratify requests according to their level
reg [W_REQ-1:0] req_stratified [0:N_PRIORITIES-1];
reg [N_PRIORITIES-1:0] level_has_req;
always @ (*) begin: stratify
reg signed [31:0] i, j;
for (i = 0; i < N_PRIORITIES; i = i + 1) begin
for (j = 0; j < W_REQ; j = j + 1) begin
req_stratified[i][j] = req[j] &&
pri[W_PRIORITY * j +: W_PRIORITY] == i[W_PRIORITY-1:0];
end
level_has_req[i] = |req_stratified[i];
end
end
// 2. Select the highest level with active requests
wire [N_PRIORITIES-1:0] active_layer_sel;
hazard3_onehot_priority #(
.W_REQ (N_PRIORITIES),
.HIGHEST_WINS (PRIORITY_HIGHEST_WINS)
) prisel_layer (
.req (level_has_req),
.gnt (active_layer_sel)
);
// 3. Mask only those requests at this level
reg [W_REQ-1:0] reqs_from_highest_layer;
always @ (*) begin: mux_reqs_by_layer
integer i;
reqs_from_highest_layer = {W_REQ{1'b0}};
for (i = 0; i < N_PRIORITIES; i = i + 1)
reqs_from_highest_layer = reqs_from_highest_layer |
(req_stratified[i] & {W_REQ{active_layer_sel[i]}});
end
// 4. Do a standard priority select on those requests as a tie break
hazard3_onehot_priority #(
.W_REQ (W_REQ),
.HIGHEST_WINS (TIEBREAK_HIGHEST_WINS)
) prisel_tiebreak (
.req (reqs_from_highest_layer),
.gnt (gnt)
);
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+41
View File
@@ -0,0 +1,41 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// req: bitmap
// gnt: index of least set bit (HIGHEST_WINS=0) or most set bit (HIGHEST_WINS=1)
`default_nettype none
module hazard3_priority_encode #(
parameter W_REQ = 16,
parameter HIGHEST_WINS = 0,
parameter W_GNT = $clog2(W_REQ) // do not modify
) (
input wire [W_REQ-1:0] req,
output wire [W_GNT-1:0] gnt
);
wire [W_REQ-1:0] gnt_onehot;
hazard3_onehot_priority #(
.W_REQ (W_REQ),
.HIGHEST_WINS (HIGHEST_WINS)
) priority_u (
.req (req),
.gnt (gnt_onehot)
);
hazard3_onehot_encode #(
.W_REQ (W_REQ)
) encode_u (
.req (gnt_onehot),
.gnt (gnt)
);
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+66
View File
@@ -0,0 +1,66 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Implement the three shifts (left logical, right logical, right arithmetic)
// using a single log-type barrel shifter. Around 240 LUTs for 32 bits.
// (7 layers of 32 2-input muxes, some extra LUTs and LUT inputs used for arith)
`default_nettype none
module hazard3_shift_barrel #(
`include "hazard3_config.vh"
,
`include "hazard3_width_const.vh"
) (
input wire [W_DATA-1:0] din,
input wire [W_SHAMT-1:0] shamt,
input wire right_nleft,
input wire rotate,
input wire arith,
output reg [W_DATA-1:0] dout
);
reg [W_DATA-1:0] din_rev;
reg [W_DATA-1:0] shift_accum;
reg sext; // haha
always @ (*) begin: shift
integer i;
for (i = 0; i < W_DATA; i = i + 1)
din_rev[i] = right_nleft ? din[W_DATA - 1 - i] : din[i];
sext = arith && din_rev[0];
shift_accum = din_rev;
for (i = 0; i < W_SHAMT; i = i + 1) begin
if (shamt[i]) begin
shift_accum = (shift_accum << (1 << i)) |
({W_DATA{sext}} & ~({W_DATA{1'b1}} << (1 << i))) |
({W_DATA{rotate && |EXTENSION_ZBB}} & (shift_accum >> (W_DATA - (1 << i))));
end
end
for (i = 0; i < W_DATA; i = i + 1)
dout[i] = right_nleft ? shift_accum[W_DATA - 1 - i] : shift_accum[i];
end
`ifdef HAZARD3_ASSERTIONS
always @ (*) begin
if (right_nleft && arith && !rotate) begin: asr
assert($signed(dout) == $signed(din) >>> $signed(shamt));
end else if (right_nleft && !arith && !rotate) begin
assert(dout == din >> shamt);
end else if (!right_nleft && !arith && !rotate) begin
assert(dout == din << shamt);
end
end
`endif
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
# Quick reference model for sequential unsigned multiply/divide/modulo
def div_step(w, accum, divisor):
sub_tmp = accum - (divisor << (w - 1))
underflow = sub_tmp < 0
if not underflow:
accum = sub_tmp
accum = (accum << 1) | (not underflow)
return accum
def divmod(w, dividend, divisor, debug=True):
accum = dividend
for i in range(w):
accum_prev = accum
accum = div_step(w, accum, divisor)
if debug:
print("Step {:02d}: accum {:0{}x} -> {:0{}x}".format(
i, accum_prev, int(w / 2), accum, int(w / 2)))
return (accum >> w, accum & ((1 << w) - 1))
def mul_step(w, accum, multiplicand):
add_en = accum & 1
accum = accum >> 1
if add_en:
accum += (multiplicand << (w - 1))
return accum
def mul(w, multiplicand, multiplier, debug=True):
accum = multiplier
for i in range(w):
accum_prev = accum
accum = mul_step(w, accum, multiplicand)
if debug:
print("Step {:02d}: accum {:0{}x} -> {:0{}x}".format(
i, accum_prev, int(w / 2), accum, int(w / 2)))
return (accum >> w, accum & ((1 << w) - 1))
def divtest(w=4):
for i in range(2 ** w):
for j in range(1, 2 ** w):
gatemod, gatediv = divmod(w, i, j, debug=False)
goldmod, golddiv = (i % j, i // j)
print("{:02d} % {:02d} = {:02d} (gold {:02d}); ./. = {:02d} (gold {:02d})"
.format(i, j, gatemod, goldmod, gatediv, golddiv))
assert(gatemod == goldmod)
assert(gatediv == golddiv)
def multest(w=4):
for i in range(2 ** w):
for j in range(2 ** w):
gateh, gatel = mul(w, i, j, debug=False)
gold = i * j
goldl, goldh = (gold & ((1 << w) - 1), gold >> w)
print("{:02d} * {:02d} = ({:02d} (gold {:02d}), {:02d} (gold {:02d})"
.format(i, j, gateh, goldh, gatel, goldl))
assert(gatel == goldl)
assert(gateh == goldh)
if __name__ == "__main__":
print("Test division:")
divtest()
print("Test multiplication:")
multest()
+200
View File
@@ -0,0 +1,200 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// APB-to-APB asynchronous bridge for connecting DTM to DM, in case DTM is in
// a different clock domain (e.g. running directly from crystal to get a
// fixed baud reference)
//
// Note this module depends on the hazard3_sync_1bit module (a flop-chain
// synchroniser) which should be reimplemented for your FPGA/process.
`ifndef HAZARD3_REG_KEEP_ATTRIBUTE
`define HAZARD3_REG_KEEP_ATTRIBUTE (* keep = 1'b1 *)
`endif
`default_nettype none
module hazard3_apb_async_bridge #(
parameter W_ADDR = 8,
parameter W_DATA = 32,
parameter N_SYNC_STAGES = 2
) (
// Resets assumed to be synchronised externally
input wire clk_src,
input wire rst_n_src,
input wire clk_dst,
input wire rst_n_dst,
// APB port from Transport Module
input wire src_psel,
input wire src_penable,
input wire src_pwrite,
input wire [W_ADDR-1:0] src_paddr,
input wire [W_DATA-1:0] src_pwdata,
output wire [W_DATA-1:0] src_prdata,
output wire src_pready,
output wire src_pslverr,
// APB port to Debug Module
output wire dst_psel,
output wire dst_penable,
output wire dst_pwrite,
output wire [W_ADDR-1:0] dst_paddr,
output wire [W_DATA-1:0] dst_pwdata,
input wire [W_DATA-1:0] dst_prdata,
input wire dst_pready,
input wire dst_pslverr
);
// ----------------------------------------------------------------------------
// Clock-crossing registers
// We're using a modified req/ack handshake:
//
// - Initially both req and ack are low
// - src asserts req high
// - dst responds with ack high and begins transfer
// - src deasserts req once it sees ack high
// - dst deasserts ack once:
// - transfer is complete *and*
// - dst sees req deasserted
// - Once src sees ack low, a new transfer can begin.
//
// A NRZI toggle handshake might be more appropriate, but can cause spurious
// bus accesses when only one side of the link is reset.
`HAZARD3_REG_KEEP_ATTRIBUTE reg src_req;
wire dst_req;
`HAZARD3_REG_KEEP_ATTRIBUTE reg dst_ack;
wire src_ack;
// Note the launch registers are not resettable. We maintain setup/hold on
// launch-to-capture paths thanks to the req/ack handshake. A stray reset
// could violate this.
//
// The req/ack logic itself can be reset safely because the receiving domain
// is protected from metastability by a 2FF synchroniser.
`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_ADDR + W_DATA + 1 -1:0] src_paddr_pwdata_pwrite; // launch
`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_ADDR + W_DATA + 1 -1:0] dst_paddr_pwdata_pwrite; // capture
`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_DATA + 1 -1:0] dst_prdata_pslverr; // launch
`HAZARD3_REG_KEEP_ATTRIBUTE reg [W_DATA + 1 -1:0] src_prdata_pslverr; // capture
hazard3_sync_1bit #(
.N_STAGES (N_SYNC_STAGES)
) sync_req (
.clk (clk_dst),
.rst_n (rst_n_dst),
.i (src_req),
.o (dst_req)
);
hazard3_sync_1bit #(
.N_STAGES (N_SYNC_STAGES)
) sync_ack (
.clk (clk_src),
.rst_n (rst_n_src),
.i (dst_ack),
.o (src_ack)
);
// ----------------------------------------------------------------------------
// src state machine
reg src_waiting_for_downstream;
reg src_pready_r;
always @ (posedge clk_src or negedge rst_n_src) begin
if (!rst_n_src) begin
src_req <= 1'b0;
src_waiting_for_downstream <= 1'b0;
src_prdata_pslverr <= {W_DATA + 1{1'b0}};
src_pready_r <= 1'b1;
end else if (src_waiting_for_downstream) begin
if (src_req && src_ack) begin
// Request was acknowledged, so deassert.
src_req <= 1'b0;
end else if (!(src_req || src_ack)) begin
// Downstream transfer has finished, data is valid.
src_pready_r <= 1'b1;
src_waiting_for_downstream <= 1'b0;
// Note this assignment is cross-domain (but data has been stable
// for duration of ack synchronisation delay):
src_prdata_pslverr <= dst_prdata_pslverr;
end
end else begin
// paddr, pwdata and pwrite are all valid during the setup phase, and
// APB defines the setup phase to always last one cycle and proceed
// to access phase. So, we can ignore penable, and pready is ignored.
if (src_psel) begin
src_pready_r <= 1'b0;
src_req <= 1'b1;
src_waiting_for_downstream <= 1'b1;
end
end
end
// Bus request launch register is not resettable
always @ (posedge clk_src) begin
if (src_psel && !src_waiting_for_downstream)
src_paddr_pwdata_pwrite <= {src_paddr, src_pwdata, src_pwrite};
end
assign {src_prdata, src_pslverr} = src_prdata_pslverr;
assign src_pready = src_pready_r;
// ----------------------------------------------------------------------------
// dst state machine
wire dst_bus_finish = dst_penable && dst_pready;
reg dst_psel_r;
reg dst_penable_r;
always @ (posedge clk_dst or negedge rst_n_dst) begin
if (!rst_n_dst) begin
dst_ack <= 1'b0;
end else if (dst_req) begin
dst_ack <= 1'b1;
end else if (!dst_req && dst_ack && !dst_psel_r) begin
dst_ack <= 1'b0;
end
end
always @ (posedge clk_dst or negedge rst_n_dst) begin
if (!rst_n_dst) begin
dst_psel_r <= 1'b0;
dst_penable_r <= 1'b0;
dst_paddr_pwdata_pwrite <= {W_ADDR + W_DATA + 1{1'b0}};
end else if (dst_req && !dst_ack) begin
dst_psel_r <= 1'b1;
// Note this assignment is cross-domain. The src register has been
// stable for the duration of the req sync delay.
dst_paddr_pwdata_pwrite <= src_paddr_pwdata_pwrite;
end else if (dst_psel_r && !dst_penable_r) begin
dst_penable_r <= 1'b1;
end else if (dst_bus_finish) begin
dst_psel_r <= 1'b0;
dst_penable_r <= 1'b0;
end
end
// Bus response launch register is not resettable
always @ (posedge clk_dst) begin
if (dst_bus_finish)
dst_prdata_pslverr <= {dst_prdata, dst_pslverr};
end
assign dst_psel = dst_psel_r;
assign dst_penable = dst_penable_r;
assign {dst_paddr, dst_pwdata, dst_pwrite} = dst_paddr_pwdata_pwrite;
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+41
View File
@@ -0,0 +1,41 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// The output is asserted asynchronously when the input is asserted,
// but deasserted synchronously when clocked with the input deasserted.
// Input and output are both active-low.
//
// This is a baseline implementation -- you should replace it with cells
// specific to your FPGA/process
`ifndef HAZARD3_REG_KEEP_ATTRIBUTE
`define HAZARD3_REG_KEEP_ATTRIBUTE (* keep = 1'b1 *)
`endif
`default_nettype none
module hazard3_reset_sync #(
parameter N_STAGES = 2 // Should be >= 2
) (
input wire clk,
input wire rst_n_in,
output wire rst_n_out
);
`HAZARD3_REG_KEEP_ATTRIBUTE reg [N_STAGES-1:0] delay;
always @ (posedge clk or negedge rst_n_in)
if (!rst_n_in)
delay <= {N_STAGES{1'b0}};
else
delay <= {delay[N_STAGES-2:0], 1'b1};
assign rst_n_out = delay[N_STAGES-1];
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+39
View File
@@ -0,0 +1,39 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// A 2FF synchronizer to mitigate metastabilities. This is a baseline
// implementation -- you should replace it with cells specific to your
// FPGA/process
`ifndef HAZARD3_REG_KEEP_ATTRIBUTE
`define HAZARD3_REG_KEEP_ATTRIBUTE (* keep = 1'b1 *) (* async_reg *)
`endif
`default_nettype none
module hazard3_sync_1bit #(
parameter N_STAGES = 2 // Should be >=2
) (
input wire clk,
input wire rst_n,
input wire i,
output wire o
);
`HAZARD3_REG_KEEP_ATTRIBUTE reg [N_STAGES-1:0] sync_flops;
always @ (posedge clk or negedge rst_n)
if (!rst_n)
sync_flops <= {N_STAGES{1'b0}};
else
sync_flops <= {sync_flops[N_STAGES-2:0], i};
assign o = sync_flops[N_STAGES-1];
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+1
View File
@@ -0,0 +1 @@
file hazard3_dm.v
+904
View File
@@ -0,0 +1,904 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// RISC-V Debug Module for Hazard3. Supports up to 32 cores (1 hart per core).
`default_nettype none
module hazard3_dm #(
// Where there are multiple harts per DM, the least-indexed hart is the
// least-significant on each concatenated hart access bus.
parameter N_HARTS = 1,
// Where there are multiple DMs, the address of each DM should be a
// multiple of 'h200, so that bits[8:2] decode correctly.
parameter NEXT_DM_ADDR = 32'h0000_0000,
// Implement support for system bus access:
parameter HAVE_SBA = 0,
// Do not modify:
parameter XLEN = 32, // Do not modify
parameter W_HARTSEL = N_HARTS > 1 ? $clog2(N_HARTS) : 1 // Do not modify
) (
// DM is assumed to be in same clock domain as core; clock crossing
// (if any) is inside DTM, or between DTM and DM.
input wire clk,
input wire rst_n,
// APB access from Debug Transport Module
input wire dmi_psel,
input wire dmi_penable,
input wire dmi_pwrite,
input wire [8:0] dmi_paddr,
input wire [31:0] dmi_pwdata,
output reg [31:0] dmi_prdata,
output wire dmi_pready,
output wire dmi_pslverr,
// Reset request/acknowledge. "req" is a pulse >= 1 cycle wide. "done" is
// level-sensitive, goes high once component is out of reset.
//
// The "sys" reset (ndmreset) is conventionally everything apart from DM +
// DTM, but, as per section 3.2 in 0.13.2 debug spec: "Exactly what is
// affected by this reset is implementation dependent, as long as it is
// possible to debug programs from the first instruction executed." So
// this could simply be an all-hart reset.
output wire sys_reset_req,
input wire sys_reset_done,
output wire [N_HARTS-1:0] hart_reset_req,
input wire [N_HARTS-1:0] hart_reset_done,
// Hart run/halt control
output wire [N_HARTS-1:0] hart_req_halt,
output wire [N_HARTS-1:0] hart_req_halt_on_reset,
output wire [N_HARTS-1:0] hart_req_resume,
input wire [N_HARTS-1:0] hart_halted,
input wire [N_HARTS-1:0] hart_running,
// Hart access to data0 CSR (assumed to be core-internal but per-hart)
output wire [N_HARTS*XLEN-1:0] hart_data0_rdata,
input wire [N_HARTS*XLEN-1:0] hart_data0_wdata,
input wire [N_HARTS-1:0] hart_data0_wen,
// Hart instruction injection
output wire [N_HARTS*32-1:0] hart_instr_data,
output reg [N_HARTS-1:0] hart_instr_data_vld,
input wire [N_HARTS-1:0] hart_instr_data_rdy,
input wire [N_HARTS-1:0] hart_instr_caught_exception,
input wire [N_HARTS-1:0] hart_instr_caught_ebreak,
// System bus access (optional) -- can be hooked up to the standalone AHB
// shim (hazard3_sbus_to_ahb.v) or the SBA input port on the processor
// wrapper, which muxes SBA into the processor's load/store bus access
// port. SBA does not increase debugger bus throughput, but supports
// minimally intrusive debug bus access for e.g. Segger RTT.
output wire [31:0] sbus_addr,
output wire sbus_write,
output wire [1:0] sbus_size,
output wire sbus_vld,
input wire sbus_rdy,
input wire sbus_err,
output wire [31:0] sbus_wdata,
input wire [31:0] sbus_rdata
);
wire dmi_write = dmi_psel && dmi_penable && dmi_pready && dmi_pwrite;
wire dmi_read = dmi_psel && dmi_penable && dmi_pready && !dmi_pwrite;
assign dmi_pready = 1'b1;
assign dmi_pslverr = 1'b0;
// Program buffer is fixed at 2 words plus impebreak. The main thing we care
// about is support for efficient memory block transfers using abstractauto;
// in this case 2 words + impebreak is sufficient for RV32I, and 1 word +
// impebreak is sufficient for RV32IC.
localparam PROGBUF_SIZE = 2;
// ----------------------------------------------------------------------------
// Address constants
localparam ADDR_DATA0 = 7'h04;
// Other data registers not present.
localparam ADDR_DMCONTROL = 7'h10;
localparam ADDR_DMSTATUS = 7'h11;
localparam ADDR_HARTINFO = 7'h12;
localparam ADDR_HALTSUM1 = 7'h13;
localparam ADDR_HALTSUM0 = 7'h40;
// No HALTSUM2+ registers (we don't support >32 harts anyway)
localparam ADDR_HAWINDOWSEL = 7'h14;
localparam ADDR_HAWINDOW = 7'h15;
localparam ADDR_ABSTRACTCS = 7'h16;
localparam ADDR_COMMAND = 7'h17;
localparam ADDR_ABSTRACTAUTO = 7'h18;
localparam ADDR_CONFSTRPTR0 = 7'h19;
localparam ADDR_CONFSTRPTR1 = 7'h1a;
localparam ADDR_CONFSTRPTR2 = 7'h1b;
localparam ADDR_CONFSTRPTR3 = 7'h1c;
localparam ADDR_NEXTDM = 7'h1d;
localparam ADDR_PROGBUF0 = 7'h20;
localparam ADDR_PROGBUF1 = 7'h21;
// No authentication
localparam ADDR_SBCS = 7'h38;
localparam ADDR_SBADDRESS0 = 7'h39;
localparam ADDR_SBDATA0 = 7'h3c;
// APB is byte-addressed, DM registers are word-addressed.
wire [6:0] dmi_regaddr = dmi_paddr[8:2];
// ----------------------------------------------------------------------------
// Hart selection
reg dmactive;
// Some fiddliness to make sure we get a single-wide zero-valued signal when
// N_HARTS == 1 (so we can use this for indexing of per-hart signals)
reg [W_HARTSEL-1:0] hartsel;
wire [W_HARTSEL-1:0] hartsel_next;
generate
if (N_HARTS > 1) begin: has_hartsel
// Only the lower 10 bits of hartsel are supported
assign hartsel_next = dmi_write && dmi_regaddr == ADDR_DMCONTROL ?
dmi_pwdata[16 +: W_HARTSEL] : hartsel;
end else begin: has_no_hartsel
assign hartsel_next = 1'b0;
end
endgenerate
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
hartsel <= {W_HARTSEL{1'b0}};
end else if (!dmactive) begin
hartsel <= {W_HARTSEL{1'b0}};
end else begin
hartsel <= hartsel_next;
end
end
// Also implement the hart array mask if there is more than one hart.
reg [N_HARTS-1:0] hart_array_mask;
reg hasel;
wire [N_HARTS-1:0] hart_array_mask_next;
wire hasel_next;
generate
if (N_HARTS > 1) begin: has_array_mask
assign hart_array_mask_next = dmi_write && dmi_regaddr == ADDR_HAWINDOW ?
dmi_pwdata[N_HARTS-1:0] : hart_array_mask;
assign hasel_next = dmi_write && dmi_regaddr == ADDR_DMCONTROL ?
dmi_pwdata[26] : hasel;
end else begin: has_no_array_mask
assign hart_array_mask_next = 1'b0;
assign hasel_next = 1'b0;
end
endgenerate
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
hart_array_mask <= {N_HARTS{1'b0}};
hasel <= 1'b0;
end else if (!dmactive) begin
hart_array_mask <= {N_HARTS{1'b0}};
hasel <= 1'b0;
end else begin
hart_array_mask <= hart_array_mask_next;
hasel <= hasel_next;
end
end
// ----------------------------------------------------------------------------
// Run/halt/reset control
// Normal read/write fields for dmcontrol (note some of these are per-hart
// fields that get rotated into dmcontrol based on the current/next hartsel).
reg [N_HARTS-1:0] dmcontrol_haltreq;
reg [N_HARTS-1:0] dmcontrol_hartreset;
reg [N_HARTS-1:0] dmcontrol_resethaltreq;
reg dmcontrol_ndmreset;
wire [N_HARTS-1:0] dmcontrol_op_mask;
generate
if (N_HARTS > 1) begin: dmcontrol_multiple_harts
// Selection is the hart selected by hartsel, *plus* the hart array mask
// if hasel is set. Note we don't need to use the "next" version of
// hart_array_mask since it can't change simultaneously with dmcontrol.
assign dmcontrol_op_mask =
(hartsel_next >= N_HARTS ? {N_HARTS{1'b0}} : {{N_HARTS-1{1'b0}}, 1'b1} << hartsel_next)
| ({N_HARTS{hasel_next}} & hart_array_mask);
end else begin: dmcontrol_single_hart
assign dmcontrol_op_mask = 1'b1;
end
endgenerate
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
dmactive <= 1'b0;
dmcontrol_ndmreset <= 1'b0;
dmcontrol_haltreq <= {N_HARTS{1'b0}};
dmcontrol_hartreset <= {N_HARTS{1'b0}};
dmcontrol_resethaltreq <= {N_HARTS{1'b0}};
end else if (!dmactive) begin
// Only dmactive is writable when !dmactive
if (dmi_write && dmi_regaddr == ADDR_DMCONTROL)
dmactive <= dmi_pwdata[0];
dmcontrol_ndmreset <= 1'b0;
dmcontrol_haltreq <= {N_HARTS{1'b0}};
dmcontrol_hartreset <= {N_HARTS{1'b0}};
dmcontrol_resethaltreq <= {N_HARTS{1'b0}};
end else if (dmi_write && dmi_regaddr == ADDR_DMCONTROL) begin
dmactive <= dmi_pwdata[0];
dmcontrol_ndmreset <= dmi_pwdata[1];
dmcontrol_haltreq <= (dmcontrol_haltreq & ~dmcontrol_op_mask) |
({N_HARTS{dmi_pwdata[31]}} & dmcontrol_op_mask);
dmcontrol_hartreset <= (dmcontrol_hartreset & ~dmcontrol_op_mask) |
({N_HARTS{dmi_pwdata[29]}} & dmcontrol_op_mask);
dmcontrol_resethaltreq <= (dmcontrol_resethaltreq
& ~({N_HARTS{dmi_pwdata[2]}} & dmcontrol_op_mask))
| ({N_HARTS{dmi_pwdata[3]}} & dmcontrol_op_mask);
end
end
assign sys_reset_req = dmcontrol_ndmreset;
assign hart_reset_req = dmcontrol_hartreset;
assign hart_req_halt = dmcontrol_haltreq;
assign hart_req_halt_on_reset = dmcontrol_resethaltreq;
reg [N_HARTS-1:0] hart_reset_done_prev;
reg [N_HARTS-1:0] dmstatus_havereset;
wire [N_HARTS-1:0] hart_available = hart_reset_done & {N_HARTS{sys_reset_done}};
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
hart_reset_done_prev <= {N_HARTS{1'b0}};
end else begin
hart_reset_done_prev <= hart_reset_done;
end
end
wire dmcontrol_ackhavereset = dmi_write && dmi_regaddr == ADDR_DMCONTROL && dmi_pwdata[28];
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
dmstatus_havereset <= {N_HARTS{1'b0}};
end else if (!dmactive) begin
dmstatus_havereset <= {N_HARTS{1'b0}};
end else begin
dmstatus_havereset <= (dmstatus_havereset | (hart_reset_done & ~hart_reset_done_prev))
& ~({N_HARTS{dmcontrol_ackhavereset}} & dmcontrol_op_mask);
end
end
reg [N_HARTS-1:0] dmstatus_resumeack;
reg [N_HARTS-1:0] dmcontrol_resumereq_sticky;
// Note: we are required to ignore resumereq when haltreq is also set, as per
// spec (odd since the host is forbidden from writing both at once anyway).
// The wording is odd, it refers only to `haltreq` which is specifically the
// write-only `dmcontrol` field, not the underlying halt request state bits.
wire dmcontrol_resumereq = dmi_write && dmi_regaddr == ADDR_DMCONTROL &&
dmi_pwdata[30] && !dmi_pwdata[31];
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
dmstatus_resumeack <= {N_HARTS{1'b0}};
dmcontrol_resumereq_sticky <= {N_HARTS{1'b0}};
end else if (!dmactive) begin
dmstatus_resumeack <= {N_HARTS{1'b0}};
dmcontrol_resumereq_sticky <= {N_HARTS{1'b0}};
end else begin
dmstatus_resumeack <= (dmstatus_resumeack
| (dmcontrol_resumereq_sticky & hart_running & hart_available))
& ~({N_HARTS{dmcontrol_resumereq}} & dmcontrol_op_mask);
dmcontrol_resumereq_sticky <= (dmcontrol_resumereq_sticky
& ~(hart_running & hart_available))
| ({N_HARTS{dmcontrol_resumereq}} & dmcontrol_op_mask);
end
end
assign hart_req_resume = dmcontrol_resumereq_sticky;
// ----------------------------------------------------------------------------
// System bus access
reg [31:0] sbaddress;
reg [31:0] sbdata;
// Update logic for address/data registers:
reg sbbusy;
reg sbautoincrement;
reg [2:0] sbaccess; // Size of the transfer
wire sbdata_write_blocked;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
sbaddress <= {32{1'b0}};
sbdata <= {32{1'b0}};
end else if (!dmactive) begin
sbaddress <= {32{1'b0}};
sbdata <= {32{1'b0}};
end else if (HAVE_SBA) begin
if (dmi_write && dmi_regaddr == ADDR_SBDATA0 && !sbdata_write_blocked) begin
// Note sbbusyerror and sberror block writes to sbdata0, as the
// write is required to have no side effects when they are set.
sbdata <= dmi_pwdata;
end else if (sbus_vld && sbus_rdy && !sbus_write && !sbus_err) begin
// Make sure the lower byte lanes see appropriately shifted data as
// long as the transfer is naturally aligned
sbdata <= sbaddress[1:0] == 2'b01 ? {sbus_rdata[31:8], sbus_rdata[15:8]} :
sbaddress[1:0] == 2'b10 ? {sbus_rdata[31:16], sbus_rdata[31:16]} :
sbaddress[1:0] == 2'b11 ? {sbus_rdata[31:8], sbus_rdata[31:24]} : sbus_rdata;
end
if (dmi_write && dmi_regaddr == ADDR_SBADDRESS0 && !sbbusy) begin
// Note sbaddress can't be written when busy, but
// sberror/sbbusyerror do not prevent writes.
sbaddress <= dmi_pwdata;
end else if (sbus_vld && sbus_rdy && !sbus_err && sbautoincrement) begin
// Note: address increments only following a successful transfer.
// Spec 0.13.2 weirdly implies address should increment following
// a sbdata0 read with sbautoincrement=1 and sbreadondata=0, but
// this seems to be a typo, fixed in later versions.
sbaddress <= sbaddress + (
sbaccess[1:0] == 2'b00 ? 32'd1 :
sbaccess[1:0] == 2'b01 ? 32'd2 : 32'd4
);
end
end
end
// Control logic:
reg sbbusyerror;
reg sbreadonaddr;
reg sbreadondata;
reg [2:0] sberror;
reg sb_current_is_write;
localparam SBERROR_OK = 3'h0;
localparam SBERROR_BADADDR = 3'h2;
localparam SBERROR_BADALIGN = 3'h3;
localparam SBERROR_BADSIZE = 3'h4;
assign sbdata_write_blocked = sbbusy || sbbusyerror || |sberror;
// Notes on behaviour of sbbusyerror: the sbbusyerror description says:
//
// "Set when the debugger attempts to read data while a read is in progress,
// or when the debugger initiates a new access while one is already in
// progress (while sbbusy is set)."
//
// However, sbaddress0 description says:
//
// "When the system bus master is busy, writes to this register will set
// sbbusyerror and dont do anything else."
//
// ...not conditioned on sbreadonaddr. Likewise the sbdata0 description says:
//
// "If the bus master is busy then accesses set sbbusyerror, and dont do
// anything else."
//
// ...not conditioned on sbreadondata. We are going to take the union of all
// the cases where the spec says we should raise an error:
wire sb_access_illegal_when_busy =
dmi_regaddr == ADDR_SBDATA0 && (dmi_read || dmi_write) ||
dmi_regaddr == ADDR_SBADDRESS0 && dmi_write;
wire sb_want_start_write = dmi_write && dmi_regaddr == ADDR_SBDATA0;
wire sb_want_start_read =
(sbreadonaddr && dmi_write && dmi_regaddr == ADDR_SBADDRESS0) ||
(sbreadondata && dmi_read && dmi_regaddr == ADDR_SBDATA0);
wire [1:0] sb_next_align = sbreadonaddr && dmi_write && dmi_regaddr == ADDR_SBADDRESS0 ?
dmi_pwdata[1:0] : sbaddress[1:0];
wire sb_badalign =
(sbaccess == 3'h1 && sb_next_align[0]) ||
(sbaccess == 3'h2 && |sb_next_align[1:0]);
wire sb_badsize = sbaccess > 3'h2;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
sbbusy <= 1'b0;
sbbusyerror <= 1'b0;
sbreadonaddr <= 1'b0;
sbreadondata <= 1'b0;
sbaccess <= 3'h0;
sbautoincrement <= 1'b0;
sberror <= 3'h0;
sb_current_is_write <= 1'b0;
end else if (!dmactive) begin
sbbusy <= 1'b0;
sbbusyerror <= 1'b0;
sbreadonaddr <= 1'b0;
sbreadondata <= 1'b0;
sbaccess <= 3'h0;
sbautoincrement <= 1'b0;
sberror <= 3'h0;
sb_current_is_write <= 1'b0;
end else if (HAVE_SBA) begin
if (dmi_write && dmi_regaddr == ADDR_SBCS) begin
// Assume a transfer is not in progress when written (per spec)
sbbusyerror <= sbbusyerror && !dmi_pwdata[22];
sbreadonaddr <= dmi_pwdata[20];
sbaccess <= dmi_pwdata[19:17];
sbautoincrement <= dmi_pwdata[16];
sbreadondata <= dmi_pwdata[15];
sberror <= sberror & ~dmi_pwdata[14:12];
end
if (sbbusy) begin
if (sb_access_illegal_when_busy) begin
sbbusyerror <= 1'b1;
end
if (sbus_vld && sbus_rdy) begin
sbbusy <= 1'b0;
if (sbus_err) begin
sberror <= SBERROR_BADADDR;
end
end
end else if ((sb_want_start_read || sb_want_start_write) && ~|sberror && !sbbusyerror) begin
if (sb_badsize) begin
sberror <= SBERROR_BADSIZE;
end else if (sb_badalign) begin
sberror <= SBERROR_BADALIGN;
end else begin
sbbusy <= 1'b1;
sb_current_is_write <= sb_want_start_write;
end
end
end
end
assign sbus_addr = sbaddress;
assign sbus_write = sb_current_is_write;
assign sbus_size = sbaccess[1:0];
assign sbus_vld = sbbusy;
// Replicate byte lanes to handle naturally-aligned cases.
assign sbus_wdata = sbaccess[1:0] == 2'b00 ? {4{sbdata[7:0]}} :
sbaccess[1:0] == 2'b01 ? {2{sbdata[15:0]}} : sbdata;
// ----------------------------------------------------------------------------
// Abstract command data registers
wire abstractcs_busy;
// The same data0 register is aliased as a CSR on all harts connected to this
// DM. Cores may read data0 as a CSR when in debug mode, and may write it when:
//
// - That core is in debug mode, and...
// - We are currently executing an abstract command on that core
//
// The DM can also read/write data0 at all times.
reg [XLEN-1:0] abstract_data0;
assign hart_data0_rdata = {N_HARTS{abstract_data0}};
always @ (posedge clk or negedge rst_n) begin: update_hart_data0
reg signed [31:0] i;
if (!rst_n) begin
abstract_data0 <= {XLEN{1'b0}};
end else if (!dmactive) begin
abstract_data0 <= {XLEN{1'b0}};
end else if (dmi_write && dmi_regaddr == ADDR_DATA0) begin
abstract_data0 <= dmi_pwdata;
end else begin
for (i = 0; i < N_HARTS; i = i + 1) begin
if (hartsel == i[W_HARTSEL-1:0] && hart_data0_wen[i] && hart_halted[i] && abstractcs_busy)
abstract_data0 <= hart_data0_wdata[i * XLEN +: XLEN];
end
end
end
reg [XLEN-1:0] progbuf0;
reg [XLEN-1:0] progbuf1;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
progbuf0 <= {XLEN{1'b0}};
progbuf1 <= {XLEN{1'b0}};
end else if (!dmactive) begin
progbuf0 <= {XLEN{1'b0}};
progbuf1 <= {XLEN{1'b0}};
end else if (dmi_write && !abstractcs_busy) begin
if (dmi_regaddr == ADDR_PROGBUF0)
progbuf0 <= dmi_pwdata;
if (dmi_regaddr == ADDR_PROGBUF1)
progbuf1 <= dmi_pwdata;
end
end
reg abstractauto_autoexecdata;
reg [1:0] abstractauto_autoexecprogbuf;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
abstractauto_autoexecdata <= 1'b0;
abstractauto_autoexecprogbuf <= 2'b00;
end else if (!dmactive) begin
abstractauto_autoexecdata <= 1'b0;
abstractauto_autoexecprogbuf <= 2'b00;
end else if (dmi_write && dmi_regaddr == ADDR_ABSTRACTAUTO) begin
abstractauto_autoexecdata <= dmi_pwdata[0];
abstractauto_autoexecprogbuf <= dmi_pwdata[17:16];
end
end
// ----------------------------------------------------------------------------
// Abstract command state machine
localparam W_STATE = 4;
localparam S_IDLE = 4'd0;
localparam S_ISSUE_REGREAD = 4'd1;
localparam S_ISSUE_REGWRITE = 4'd2;
localparam S_ISSUE_REGEBREAK = 4'd3;
localparam S_WAIT_REGEBREAK = 4'd4;
localparam S_ISSUE_PROGBUF0 = 4'd5;
localparam S_ISSUE_PROGBUF1 = 4'd6;
localparam S_ISSUE_IMPEBREAK = 4'd7;
localparam S_WAIT_IMPEBREAK = 4'd8;
localparam CMDERR_OK = 3'h0;
localparam CMDERR_BUSY = 3'h1;
localparam CMDERR_UNSUPPORTED = 3'h2;
localparam CMDERR_EXCEPTION = 3'h3;
localparam CMDERR_HALTRESUME = 3'h4;
reg [2:0] abstractcs_cmderr;
reg [2:0] abstractcs_cmderr_nxt;
reg [W_STATE-1:0] acmd_state;
reg [W_STATE-1:0] acmd_state_nxt;
assign abstractcs_busy = acmd_state != S_IDLE;
wire start_abstract_cmd = abstractcs_cmderr == CMDERR_OK && !abstractcs_busy && (
(dmi_write && dmi_regaddr == ADDR_COMMAND) ||
((dmi_write || dmi_read) && abstractauto_autoexecdata && dmi_regaddr == ADDR_DATA0) ||
((dmi_write || dmi_read) && abstractauto_autoexecprogbuf[0] && dmi_regaddr == ADDR_PROGBUF0) ||
((dmi_write || dmi_read) && abstractauto_autoexecprogbuf[1] && dmi_regaddr == ADDR_PROGBUF1)
);
wire dmi_access_illegal_when_busy =
(dmi_write && (
dmi_regaddr == ADDR_ABSTRACTCS || dmi_regaddr == ADDR_COMMAND || dmi_regaddr == ADDR_ABSTRACTAUTO ||
dmi_regaddr == ADDR_DATA0 || dmi_regaddr == ADDR_PROGBUF0 || dmi_regaddr == ADDR_PROGBUF1)) ||
(dmi_read && (
dmi_regaddr == ADDR_DATA0 || dmi_regaddr == ADDR_PROGBUF0 || dmi_regaddr == ADDR_PROGBUF1));
// Decode what acmd may be triggered on this cycle, and whether it is
// supported -- command source may be a registered version of most recent
// command (if abstractauto is used) or a fresh command off the bus. We don't
// register the entire write data; repeats of unsupported commands are
// detected by just registering that the last written command was
// unsupported.
wire acmd_new = dmi_write && dmi_regaddr == ADDR_COMMAND;
wire acmd_new_postexec = dmi_pwdata[18];
wire acmd_new_transfer = dmi_pwdata[17];
wire acmd_new_write = dmi_pwdata[16];
wire [4:0] acmd_new_regno = dmi_pwdata[4:0];
// Note: regno and aarsize are permitted to have otherwise-invalid values if
// the transfer flag is not set.
wire acmd_new_unsupported =
(dmi_pwdata[31:24] != 8'h00 ) || // Only Access Register command supported
(dmi_pwdata[22:20] != 3'h2 && acmd_new_transfer) || // Must be 32 bits in size
(dmi_pwdata[19] ) || // aarpostincrement not supported
(dmi_pwdata[15:12] != 4'h1 && acmd_new_transfer) || // Only core register access supported
(dmi_pwdata[11:5] != 7'h0 && acmd_new_transfer); // Only GPRs supported
reg acmd_prev_postexec;
reg acmd_prev_transfer;
reg acmd_prev_write;
reg [4:0] acmd_prev_regno;
reg acmd_prev_unsupported;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
acmd_prev_postexec <= 1'b0;
acmd_prev_transfer <= 1'b0;
acmd_prev_write <= 1'b0;
acmd_prev_regno <= 5'h0;
acmd_prev_unsupported <= 1'b1;
end else if (!dmactive) begin
acmd_prev_postexec <= 1'b0;
acmd_prev_transfer <= 1'b0;
acmd_prev_write <= 1'b0;
acmd_prev_regno <= 5'h0;
acmd_prev_unsupported <= 1'b1;
end else if (start_abstract_cmd && acmd_new) begin
acmd_prev_postexec <= acmd_new_postexec;
acmd_prev_transfer <= acmd_new_transfer;
acmd_prev_write <= acmd_new_write;
acmd_prev_regno <= acmd_new_regno;
acmd_prev_unsupported <= acmd_new_unsupported;
end
end
wire acmd_postexec = acmd_new ? acmd_new_postexec : acmd_prev_postexec ;
wire acmd_transfer = acmd_new ? acmd_new_transfer : acmd_prev_transfer ;
wire acmd_write = acmd_new ? acmd_new_write : acmd_prev_write ;
wire [4:0] acmd_regno = acmd_new ? acmd_new_regno : acmd_prev_regno ;
wire acmd_unsupported = acmd_new ? acmd_new_unsupported : acmd_prev_unsupported;
always @ (*) begin
// Default: no state change
acmd_state_nxt = acmd_state;
abstractcs_cmderr_nxt = abstractcs_cmderr;
if (dmi_write && dmi_regaddr == ADDR_ABSTRACTCS && !abstractcs_busy)
abstractcs_cmderr_nxt = abstractcs_cmderr & ~dmi_pwdata[10:8];
if (abstractcs_cmderr == CMDERR_OK && abstractcs_busy && dmi_access_illegal_when_busy)
abstractcs_cmderr_nxt = CMDERR_BUSY;
if (acmd_state != S_IDLE && hart_instr_caught_exception[hartsel])
abstractcs_cmderr_nxt = CMDERR_EXCEPTION;
case (acmd_state)
S_IDLE: begin
if (start_abstract_cmd) begin
if (!hart_halted[hartsel] || !hart_available[hartsel]) begin
abstractcs_cmderr_nxt = CMDERR_HALTRESUME;
end else if (acmd_unsupported) begin
abstractcs_cmderr_nxt = CMDERR_UNSUPPORTED;
end else begin
if (acmd_transfer && acmd_write)
acmd_state_nxt = S_ISSUE_REGWRITE;
else if (acmd_transfer && !acmd_write)
acmd_state_nxt = S_ISSUE_REGREAD;
else if (acmd_postexec)
acmd_state_nxt = S_ISSUE_PROGBUF0;
else
acmd_state_nxt = S_IDLE;
end
end
end
S_ISSUE_REGREAD: begin
if (hart_instr_data_rdy[hartsel])
acmd_state_nxt = S_ISSUE_REGEBREAK;
end
S_ISSUE_REGWRITE: begin
if (hart_instr_data_rdy[hartsel])
acmd_state_nxt = S_ISSUE_REGEBREAK;
end
S_ISSUE_REGEBREAK: begin
if (hart_instr_data_rdy[hartsel])
acmd_state_nxt = S_WAIT_REGEBREAK;
end
S_WAIT_REGEBREAK: begin
if (hart_instr_caught_ebreak[hartsel]) begin
if (acmd_prev_postexec)
acmd_state_nxt = S_ISSUE_PROGBUF0;
else
acmd_state_nxt = S_IDLE;
end
end
S_ISSUE_PROGBUF0: begin
if (hart_instr_data_rdy[hartsel])
acmd_state_nxt = S_ISSUE_PROGBUF1;
end
S_ISSUE_PROGBUF1: begin
if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin
acmd_state_nxt = S_IDLE;
end else if (hart_instr_data_rdy[hartsel]) begin
acmd_state_nxt = S_ISSUE_IMPEBREAK;
end
end
S_ISSUE_IMPEBREAK: begin
if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin
acmd_state_nxt = S_IDLE;
end else if (hart_instr_data_rdy[hartsel]) begin
acmd_state_nxt = S_WAIT_IMPEBREAK;
end
end
S_WAIT_IMPEBREAK: begin
if (hart_instr_caught_exception[hartsel] || hart_instr_caught_ebreak[hartsel]) begin
acmd_state_nxt = S_IDLE;
end
end
default: begin
// Unreachable
end
endcase
end
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
abstractcs_cmderr <= CMDERR_OK;
acmd_state <= S_IDLE;
end else if (!dmactive) begin
abstractcs_cmderr <= CMDERR_OK;
acmd_state <= S_IDLE;
end else begin
abstractcs_cmderr <= abstractcs_cmderr_nxt;
acmd_state <= acmd_state_nxt;
end
end
wire [N_HARTS-1:0] hart_instr_data_vld_nxt = {{N_HARTS-1{1'b0}},
acmd_state_nxt == S_ISSUE_REGREAD || acmd_state_nxt == S_ISSUE_REGWRITE || acmd_state_nxt == S_ISSUE_REGEBREAK ||
acmd_state_nxt == S_ISSUE_PROGBUF0 || acmd_state_nxt == S_ISSUE_PROGBUF1 || acmd_state_nxt == S_ISSUE_IMPEBREAK
} << hartsel;
wire [31:0] hart_instr_data_nxt =
acmd_state_nxt == S_ISSUE_REGWRITE ? 32'hbff02073 | {20'd0, acmd_regno, 7'd0} : // csrr xx, dmdata0
acmd_state_nxt == S_ISSUE_REGREAD ? 32'hbff01073 | {12'd0, acmd_regno, 15'd0} : // csrw dmdata0, xx
acmd_state_nxt == S_ISSUE_PROGBUF0 ? progbuf0 :
acmd_state_nxt == S_ISSUE_PROGBUF1 ? progbuf1 :
32'h00100073; // ebreak
reg [31:0] hart_instr_data_reg;
assign hart_instr_data = {N_HARTS{hart_instr_data_reg}};
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
hart_instr_data_vld <= 1'b0;
hart_instr_data_reg <= 32'h00000000;
end else begin
hart_instr_data_vld <= hart_instr_data_vld_nxt;
if (hart_instr_data_vld_nxt) begin
hart_instr_data_reg <= hart_instr_data_nxt;
end
end
end
// ----------------------------------------------------------------------------
// Status helper functions
function status_any;
input [N_HARTS-1:0] status_mask;
begin
status_any = status_mask[hartsel] || (hasel && |(status_mask & hart_array_mask));
end
endfunction
function status_all;
input [N_HARTS-1:0] status_mask;
begin
status_all = status_mask[hartsel] && (!hasel || ~|(~status_mask & hart_array_mask));
end
endfunction
function [1:0] status_all_any;
input [N_HARTS-1:0] status_mask;
begin
status_all_any = {
status_all(status_mask),
status_any(status_mask)
};
end
endfunction
// ----------------------------------------------------------------------------
// DMI read data mux
always @ (*) begin
case (dmi_regaddr)
ADDR_DATA0: dmi_prdata = abstract_data0;
ADDR_DMCONTROL: dmi_prdata = {
1'b0, // haltreq is a W-only field
1'b0, // resumereq is a W1 field
status_any(dmcontrol_hartreset),
1'b0, // ackhavereset is a W1 field
1'b0, // reserved
hasel,
{{10-W_HARTSEL{1'b0}}, hartsel}, // hartsello
10'h0, // hartselhi
2'h0, // reserved
2'h0, // set/clrresethaltreq are W1 fields
dmcontrol_ndmreset,
dmactive
};
ADDR_DMSTATUS: dmi_prdata = {
9'h0, // reserved
1'b1, // impebreak = 1
2'h0, // reserved
status_all_any(dmstatus_havereset), // allhavereset, anyhavereset
status_all_any(dmstatus_resumeack), // allresumeack, anyresumeack
hartsel >= N_HARTS && !(hasel && |hart_array_mask), // allnonexistent
hartsel >= N_HARTS, // anynonexistent
status_all_any(~hart_available), // allunavail, anyunavail
status_all_any(hart_running & hart_available), // allrunning, anyrunning
status_all_any(hart_halted & hart_available), // allhalted, anyhalted
1'b1, // authenticated
1'b0, // authbusy
1'b1, // hasresethaltreq = 1 (we do support it)
1'b0, // confstrptrvalid
4'd2 // version = 2: RISC-V debug spec 0.13.2
};
ADDR_HARTINFO: dmi_prdata = {
8'h0, // reserved
4'h0, // nscratch = 0
3'h0, // reserved
1'b0, // dataccess = 0, data0 is mapped to each hart's CSR space
4'h1, // datasize = 1, a single data CSR (data0) is available
12'hbff // dataaddr, placed at the top of the M-custom space since
// the spec doesn't reserve a location for it.
};
ADDR_HALTSUM0: dmi_prdata = {
{XLEN - N_HARTS{1'b0}},
hart_halted & hart_available
};
ADDR_HALTSUM1: dmi_prdata = {
{XLEN - 1{1'b0}},
|(hart_halted & hart_available)
};
ADDR_HAWINDOWSEL: dmi_prdata = 32'h00000000;
ADDR_HAWINDOW: dmi_prdata = {
{32-N_HARTS{1'b0}},
hart_array_mask
};
ADDR_ABSTRACTCS: dmi_prdata = {
3'h0, // reserved
5'd2, // progbufsize = 2
11'h0, // reserved
abstractcs_busy,
1'b0,
abstractcs_cmderr,
4'h0,
4'd1 // datacount = 1
};
ADDR_ABSTRACTAUTO: dmi_prdata = {
14'h0,
abstractauto_autoexecprogbuf, // only progbuf0,1 present
15'h0,
abstractauto_autoexecdata // only data0 present
};
ADDR_SBCS: dmi_prdata = {
3'h1, // version = 1
6'h00,
sbbusyerror,
sbbusy,
sbreadonaddr,
sbaccess,
sbautoincrement,
sbreadondata,
sberror,
7'h20, // sbasize = 32
5'b00111 // 8, 16, 32-bit transfers supported
} & {32{|HAVE_SBA}};
ADDR_SBDATA0: dmi_prdata = sbdata & {32{|HAVE_SBA}};
ADDR_SBADDRESS0: dmi_prdata = sbaddress & {32{|HAVE_SBA}};
ADDR_CONFSTRPTR0: dmi_prdata = 32'h4c296328;
ADDR_CONFSTRPTR1: dmi_prdata = 32'h20656b75;
ADDR_CONFSTRPTR2: dmi_prdata = 32'h6e657257;
ADDR_CONFSTRPTR3: dmi_prdata = 32'h31322720;
ADDR_NEXTDM: dmi_prdata = NEXT_DM_ADDR;
ADDR_PROGBUF0: dmi_prdata = progbuf0;
ADDR_PROGBUF1: dmi_prdata = progbuf1;
default: dmi_prdata = {XLEN{1'b0}};
endcase
end
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+74
View File
@@ -0,0 +1,74 @@
/*****************************************************************************\
| Copyright (C) 2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Standalone bus shim for connecting the DM's System Bus Access to AHB
`default_nettype none
module hazard3_sbus_to_ahb #(
parameter W_ADDR = 32,
parameter W_DATA = 32
) (
input wire clk,
input wire rst_n,
input wire [W_ADDR-1:0] sbus_addr,
input wire sbus_write,
input wire [1:0] sbus_size,
input wire sbus_vld,
output wire sbus_rdy,
output wire sbus_err,
input wire [W_DATA-1:0] sbus_wdata,
output wire [W_DATA-1:0] sbus_rdata,
output wire [W_ADDR-1:0] ahblm_haddr,
output wire ahblm_hwrite,
output wire [1:0] ahblm_htrans,
output wire [2:0] ahblm_hsize,
output wire [2:0] ahblm_hburst,
output wire [3:0] ahblm_hprot,
output wire ahblm_hmastlock,
input wire ahblm_hready,
input wire ahblm_hresp,
output wire [W_DATA-1:0] ahblm_hwdata,
input wire [W_DATA-1:0] ahblm_hrdata
);
// Most signals are simple tie-throughs
assign ahblm_haddr = sbus_addr;
assign ahblm_hwrite = sbus_write;
assign ahblm_hsize = {1'b0, sbus_size};
assign ahblm_hwdata = sbus_wdata;
// HPROT = noncacheable nonbufferable privileged data access:
assign ahblm_hprot = 4'b0011;
assign ahblm_hmastlock = 1'b0;
assign ahblm_hburst = 3'h0;
assign sbus_err = ahblm_hresp;
assign sbus_rdata = ahblm_hrdata;
// Handshaking
reg dph_active;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
dph_active <= 1'b0;
end else if (ahblm_hready) begin
dph_active <= ahblm_htrans[1];
end
end
assign ahblm_htrans = sbus_vld && !dph_active ? 2'b10 : 2'b00;
assign sbus_rdy = ahblm_hready && dph_active;
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+6
View File
@@ -0,0 +1,6 @@
file hazard3_ecp5_jtag_dtm.v
file hazard3_jtag_dtm_core.v
file ../cdc/hazard3_apb_async_bridge.v
file ../cdc/hazard3_reset_sync.v
file ../cdc/hazard3_sync_1bit.v
+194
View File
@@ -0,0 +1,194 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// The ECP5 JTAGG primitive (yes that is the correct spelling) allows you to
// add two custom DRs to the FPGA's chip TAP, selected using the 8-bit ER1
// (0x32) and ER2 (0x38) instructions.
//
// Brian Swetland pointed out on Twitter that the standard RISC-V JTAG-DTM
// only uses two DRs (DTMCS and DMI), besides the standard IDCODE and BYPASS
// which are provided already by the ECP5 TAP. This file instantiates the
// guts of Hazard3's standard JTAG-DTM and connects the DTMCS and DMI
// registers to the JTAGG primitive's ER1/ER2 DRs.
//
// The exciting part is that upstream OpenOCD already allows you to set the IR
// length *and* set custom DTMCS/DMI IR values for RISC-V JTAG DTMs. This
// means with the right config file, you can access a debug module hung from
// the ECP5 TAP in this fashion using only upstream OpenOCD and gdb.
`default_nettype none
module hazard3_ecp5_jtag_dtm #(
parameter DTMCS_IDLE_HINT = 3'd4,
parameter W_PADDR = 9,
parameter ABITS = W_PADDR - 2 // do not modify
) (
// This is synchronous to TCK and asserted for one TCK cycle only
output wire dmihardreset_req,
// Bus clock + reset for Debug Module Interface
input wire clk_dmi,
input wire rst_n_dmi,
// Debug Module Interface (APB)
output wire dmi_psel,
output wire dmi_penable,
output wire dmi_pwrite,
output wire [W_PADDR-1:0] dmi_paddr,
output wire [31:0] dmi_pwdata,
input wire [31:0] dmi_prdata,
input wire dmi_pready,
input wire dmi_pslverr
);
// Signals to/from the ECP5 TAP
wire jtdo2;
wire jtdo1;
wire jtdi;
wire jtck_posedge_dont_use;
wire jshift;
wire jupdate;
wire jrst_n;
wire jce2;
wire jce1;
JTAGG jtag_u (
.JTDO2 (jtdo2),
.JTDO1 (jtdo1),
.JTDI (jtdi),
.JTCK (jtck_posedge_dont_use),
.JRTI2 (/* unused */),
.JRTI1 (/* unused */),
.JSHIFT (jshift),
.JUPDATE (jupdate),
.JRSTN (jrst_n),
.JCE2 (jce2),
.JCE1 (jce1)
);
// JTAGG primitive asserts its signals synchronously to JTCK's posedge, but
// you get weird and inconsistent results if you try to consume them
// synchronously on JTCK's posedge, possibly due to a lack of hold
// constraints in nextpnr.
//
// A quick hack is to move the sampling onto the negedge of the clock. This
// then creates more problems because we would be running our shift logic on
// a different edge from the control + CDC logic in the DTM core.
//
// So, even worse hack, move all our JTAG-domain logic onto the negedge
// (or near enough) by inverting the clock.
wire jtck = !jtck_posedge_dont_use;
localparam W_DR_SHIFT = ABITS + 32 + 2;
reg core_dr_wen;
reg core_dr_ren;
reg core_dr_sel_dmi_ndtmcs;
reg dr_shift_en;
wire [W_DR_SHIFT-1:0] core_dr_wdata;
wire [W_DR_SHIFT-1:0] core_dr_rdata;
// Decode our shift controls from the interesting ECP5 ones, and re-register
// onto JTCK negedge (our posedge). Note without re-registering we observe
// them a half-cycle (effectively one cycle) too early. This is another
// consequence of the stupid JTDI thing
always @ (posedge jtck or negedge jrst_n) begin
if (!jrst_n) begin
core_dr_sel_dmi_ndtmcs <= 1'b0;
core_dr_wen <= 1'b0;
core_dr_ren <= 1'b0;
dr_shift_en <= 1'b0;
end else begin
if (jce1 || jce2)
core_dr_sel_dmi_ndtmcs <= jce2;
core_dr_ren <= (jce1 || jce2) && !jshift;
core_dr_wen <= jupdate;
dr_shift_en <= jshift;
end
end
reg [W_DR_SHIFT-1:0] dr_shift;
assign core_dr_wdata = dr_shift;
always @ (posedge jtck or negedge jrst_n) begin
if (!jrst_n) begin
dr_shift <= {W_DR_SHIFT{1'b0}};
end else if (core_dr_ren) begin
dr_shift <= core_dr_rdata;
end else if (dr_shift_en) begin
dr_shift <= {jtdi, dr_shift[W_DR_SHIFT-1:1]};
if (!core_dr_sel_dmi_ndtmcs)
dr_shift[31] <= jtdi;
end
end
// Not documented on ECP5: as well as the posedge flop on JTDI, the ECP5 puts
// a negedge flop on JTDO1, JTDO2. (Conjecture based on dicking around with a
// logic analyser.) To get JTDOx to appear with the same timing as our shifter
// LSB (which we update on every JTCK negedge) we:
//
// - Register the LSB of the *next* value of dr_shift on the JTCK posedge, so
// half a cycle earlier than the actual dr_shift update
//
// - This then gets re-registered with the pointless JTDO negedge flops, so
// that it appears with the same timing as our DR shifter update.
reg dr_shift_next_halfcycle;
always @ (negedge jtck or negedge jrst_n) begin
if (!jrst_n) begin
dr_shift_next_halfcycle <= 1'b0;
end else begin
dr_shift_next_halfcycle <=
core_dr_ren ? core_dr_rdata[0] :
dr_shift_en ? dr_shift[1] : dr_shift[0];
end
end
// We have only a single shifter for the ER1 and ER2 chains, so these are tied
// together:
assign jtdo1 = dr_shift_next_halfcycle;
assign jtdo2 = dr_shift_next_halfcycle;
// The actual DTM is in here:
hazard3_jtag_dtm_core #(
.DTMCS_IDLE_HINT (DTMCS_IDLE_HINT),
.W_ADDR (ABITS)
) inst_hazard3_jtag_dtm_core (
.tck (jtck),
.trst_n (jrst_n),
.clk_dmi (clk_dmi),
.rst_n_dmi (rst_n_dmi),
.dr_wen (core_dr_wen),
.dr_ren (core_dr_ren),
.dr_sel_dmi_ndtmcs (core_dr_sel_dmi_ndtmcs),
.dr_wdata (core_dr_wdata),
.dr_rdata (core_dr_rdata),
.dmihardreset_req (dmihardreset_req),
.dmi_psel (dmi_psel),
.dmi_penable (dmi_penable),
.dmi_pwrite (dmi_pwrite),
.dmi_paddr (dmi_paddr[W_PADDR-1:2]),
.dmi_pwdata (dmi_pwdata),
.dmi_prdata (dmi_prdata),
.dmi_pready (dmi_pready),
.dmi_pslverr (dmi_pslverr)
);
assign dmi_paddr[1:0] = 2'b00;
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+6
View File
@@ -0,0 +1,6 @@
file hazard3_jtag_dtm.v
file hazard3_jtag_dtm_core.v
file ../cdc/hazard3_apb_async_bridge.v
file ../cdc/hazard3_reset_sync.v
file ../cdc/hazard3_sync_1bit.v
+210
View File
@@ -0,0 +1,210 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Implementation of standard RISC-V JTAG-DTM with an APB Debug Module
// Interface. The TAP itself is clocked directly by JTAG TCK; a clock
// crossing is instantiated internally between the TCK domain and the DMI bus
// clock domain.
`default_nettype none
module hazard3_jtag_dtm #(
parameter IDCODE = 32'h0000_0001,
parameter DTMCS_IDLE_HINT = 3'd4,
parameter W_PADDR = 9,
parameter ABITS = W_PADDR - 2 // do not modify
) (
// Standard JTAG signals -- the JTAG hardware is clocked directly by TCK.
input wire tck,
input wire trst_n,
input wire tms,
input wire tdi,
output reg tdo,
// This is synchronous to TCK and asserted for one TCK cycle only
output wire dmihardreset_req,
// Bus clock + reset for Debug Module Interface
input wire clk_dmi,
input wire rst_n_dmi,
// Debug Module Interface (APB)
output wire dmi_psel,
output wire dmi_penable,
output wire dmi_pwrite,
output wire [W_PADDR-1:0] dmi_paddr,
output wire [31:0] dmi_pwdata,
input wire [31:0] dmi_prdata,
input wire dmi_pready,
input wire dmi_pslverr
);
// ----------------------------------------------------------------------------
// TAP state machine
reg [3:0] tap_state;
localparam S_RESET = 4'd0;
localparam S_RUN_IDLE = 4'd1;
localparam S_SELECT_DR = 4'd2;
localparam S_CAPTURE_DR = 4'd3;
localparam S_SHIFT_DR = 4'd4;
localparam S_EXIT1_DR = 4'd5;
localparam S_PAUSE_DR = 4'd6;
localparam S_EXIT2_DR = 4'd7;
localparam S_UPDATE_DR = 4'd8;
localparam S_SELECT_IR = 4'd9;
localparam S_CAPTURE_IR = 4'd10;
localparam S_SHIFT_IR = 4'd11;
localparam S_EXIT1_IR = 4'd12;
localparam S_PAUSE_IR = 4'd13;
localparam S_EXIT2_IR = 4'd14;
localparam S_UPDATE_IR = 4'd15;
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
tap_state <= S_RESET;
end else case(tap_state)
S_RESET : tap_state <= tms ? S_RESET : S_RUN_IDLE ;
S_RUN_IDLE : tap_state <= tms ? S_SELECT_DR : S_RUN_IDLE ;
S_SELECT_DR : tap_state <= tms ? S_SELECT_IR : S_CAPTURE_DR;
S_CAPTURE_DR : tap_state <= tms ? S_EXIT1_DR : S_SHIFT_DR ;
S_SHIFT_DR : tap_state <= tms ? S_EXIT1_DR : S_SHIFT_DR ;
S_EXIT1_DR : tap_state <= tms ? S_UPDATE_DR : S_PAUSE_DR ;
S_PAUSE_DR : tap_state <= tms ? S_EXIT2_DR : S_PAUSE_DR ;
S_EXIT2_DR : tap_state <= tms ? S_UPDATE_DR : S_SHIFT_DR ;
S_UPDATE_DR : tap_state <= tms ? S_SELECT_DR : S_RUN_IDLE ;
S_SELECT_IR : tap_state <= tms ? S_RESET : S_CAPTURE_IR;
S_CAPTURE_IR : tap_state <= tms ? S_EXIT1_IR : S_SHIFT_IR ;
S_SHIFT_IR : tap_state <= tms ? S_EXIT1_IR : S_SHIFT_IR ;
S_EXIT1_IR : tap_state <= tms ? S_UPDATE_IR : S_PAUSE_IR ;
S_PAUSE_IR : tap_state <= tms ? S_EXIT2_IR : S_PAUSE_IR ;
S_EXIT2_IR : tap_state <= tms ? S_UPDATE_IR : S_SHIFT_IR ;
S_UPDATE_IR : tap_state <= tms ? S_SELECT_DR : S_RUN_IDLE ;
endcase
end
// ----------------------------------------------------------------------------
// Instruction register
localparam W_IR = 5;
// All other encodings behave as BYPASS:
localparam IR_IDCODE = 5'h01;
localparam IR_DTMCS = 5'h10;
localparam IR_DMI = 5'h11;
reg [W_IR-1:0] ir_shift;
reg [W_IR-1:0] ir;
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
ir_shift <= {W_IR{1'b0}};
ir <= IR_IDCODE;
end else if (tap_state == S_RESET) begin
ir_shift <= {W_IR{1'b0}};
ir <= IR_IDCODE;
end else if (tap_state == S_CAPTURE_IR) begin
ir_shift <= ir;
end else if (tap_state == S_SHIFT_IR) begin
ir_shift <= {tdi, ir_shift[W_IR-1:1]};
end else if (tap_state == S_UPDATE_IR) begin
ir <= ir_shift;
end
end
// ----------------------------------------------------------------------------
// Data registers
// Shift register is sized to largest DR, which is DMI:
// {addr[7:0], data[31:0], op[1:0]}
localparam W_DR_SHIFT = ABITS + 32 + 2;
reg [W_DR_SHIFT-1:0] dr_shift;
// Signals to/from the DTM core, which implements the DTMCS and DMI registers
wire core_dr_wen;
wire core_dr_ren;
wire core_dr_sel_dmi_ndtmcs;
wire [W_DR_SHIFT-1:0] core_dr_wdata;
wire [W_DR_SHIFT-1:0] core_dr_rdata;
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
dr_shift <= {W_DR_SHIFT{1'b0}};
end else if (tap_state == S_SHIFT_DR) begin
dr_shift <= {tdi, dr_shift[W_DR_SHIFT-1:1]};
// Shorten DR shift chain according to IR
if (ir == IR_DMI)
dr_shift[W_DR_SHIFT - 1] <= tdi;
else if (ir == IR_IDCODE || ir == IR_DTMCS)
dr_shift[31] <= tdi;
else // BYPASS
dr_shift[0] <= tdi;
end else if (tap_state == S_CAPTURE_DR) begin
if (ir == IR_DMI || ir == IR_DTMCS) begin
dr_shift <= core_dr_rdata;
end else if (ir == IR_IDCODE) begin
dr_shift <= {{W_DR_SHIFT-32{1'b0}}, IDCODE};
end else begin // BYPASS
dr_shift <= {W_DR_SHIFT{1'b0}};
end
end
end
// Must retime shift data onto negedge before presenting on TDO
always @ (negedge tck or negedge trst_n) begin
if (!trst_n) begin
tdo <= 1'b0;
end else begin
tdo <= tap_state == S_SHIFT_IR ? ir_shift[0] :
tap_state == S_SHIFT_DR ? dr_shift[0] : 1'b0;
end
end
// ----------------------------------------------------------------------------
// Core logic and bus interface
assign core_dr_sel_dmi_ndtmcs = ir == IR_DMI;
assign core_dr_wen = (ir == IR_DMI || ir == IR_DTMCS) && tap_state == S_UPDATE_DR;
assign core_dr_ren = (ir == IR_DMI || ir == IR_DTMCS) && tap_state == S_CAPTURE_DR;
assign core_dr_wdata = dr_shift;
hazard3_jtag_dtm_core #(
.DTMCS_IDLE_HINT (DTMCS_IDLE_HINT),
.W_ADDR (ABITS)
) dtm_core (
.tck (tck),
.trst_n (trst_n),
.clk_dmi (clk_dmi),
.rst_n_dmi (rst_n_dmi),
.dmihardreset_req (dmihardreset_req),
.dr_wen (core_dr_wen),
.dr_ren (core_dr_ren),
.dr_sel_dmi_ndtmcs (core_dr_sel_dmi_ndtmcs),
.dr_wdata (core_dr_wdata),
.dr_rdata (core_dr_rdata),
.dmi_psel (dmi_psel),
.dmi_penable (dmi_penable),
.dmi_pwrite (dmi_pwrite),
.dmi_paddr (dmi_paddr[W_PADDR-1:2]),
.dmi_pwdata (dmi_pwdata),
.dmi_prdata (dmi_prdata),
.dmi_pready (dmi_pready),
.dmi_pslverr (dmi_pslverr)
);
assign dmi_paddr[1:0] = 2'b00;
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+185
View File
@@ -0,0 +1,185 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// DTMCS + DMI control logic, bus interface and bus clock domain crossing for
// a standard RISC-V APB JTAG-DTM. Essentially everything apart from the
// actual TAP controller, IR and shift registers. Instantiated by
// hazard3_jtag_dtm.v.
//
// This core logic can be reused and connected to some other serial transport
// or, for example, the ECP5 JTAGG primitive (see hazard5_ecp5_jtag_dtm.v)
`default_nettype none
module hazard3_jtag_dtm_core #(
parameter DTMCS_IDLE_HINT = 3'd4,
parameter W_ADDR = 8,
parameter W_DR_SHIFT = W_ADDR + 32 + 2 // do not modify
) (
input wire tck,
input wire trst_n,
input wire clk_dmi,
input wire rst_n_dmi,
// DR capture/update (read/write) signals
input wire dr_wen,
input wire dr_ren,
input wire dr_sel_dmi_ndtmcs,
input wire [W_DR_SHIFT-1:0] dr_wdata,
output wire [W_DR_SHIFT-1:0] dr_rdata,
// This is synchronous to TCK and asserted for one TCK cycle only
output reg dmihardreset_req,
// Debug Module Interface (APB)
output wire dmi_psel,
output wire dmi_penable,
output wire dmi_pwrite,
output wire [W_ADDR-1:0] dmi_paddr,
output wire [31:0] dmi_pwdata,
input wire [31:0] dmi_prdata,
input wire dmi_pready,
input wire dmi_pslverr
);
wire write_dmi = dr_wen && dr_sel_dmi_ndtmcs;
wire write_dtmcs = dr_wen && !dr_sel_dmi_ndtmcs;
wire read_dmi = dr_ren && dr_sel_dmi_ndtmcs;
// ----------------------------------------------------------------------------
// DMI bus adapter
reg [1:0] dmi_cmderr;
reg dmi_busy;
// DTM-domain bus, connected to a matching DM-domain bus via an APB crossing:
wire dtm_psel;
wire dtm_penable;
wire dtm_pwrite;
wire [W_ADDR-1:0] dtm_paddr;
wire [31:0] dtm_pwdata;
wire [31:0] dtm_prdata;
wire dtm_pready;
wire dtm_pslverr;
// We are relying on some particular features of our APB clock crossing here
// to save some registers:
//
// - The transfer is launched immediately when psel is seen, no need to
// actually assert an access phase (as the standard allows the CDC to
// assume that access immediately follows setup) and no need to maintain
// pwrite/paddr/pwdata valid after the setup phase
//
// - prdata/pslverr remain valid after the transfer completes, until the next
// transfer completes
//
// These allow us to connect the upstream side of the CDC directly to our DR
// shifter without any sample/hold registers in between.
// psel is only pulsed for one cycle, penable is not asserted.
assign dtm_psel = write_dmi &&
(dr_wdata[1:0] == 2'd1 || dr_wdata[1:0] == 2'd2) &&
!(dmi_busy || dmi_cmderr != 2'd0) && dtm_pready;
assign dtm_penable = 1'b0;
// paddr/pwdata/pwrite are valid momentarily when psel is asserted.
assign dtm_paddr = dr_wdata[34 +: W_ADDR];
assign dtm_pwrite = dr_wdata[1];
assign dtm_pwdata = dr_wdata[2 +: 32];
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
dmi_busy <= 1'b0;
dmi_cmderr <= 2'd0;
end else if (read_dmi) begin
// Reading while busy sets the busy sticky error. Note the capture
// into shift register should also reflect this update on-the-fly
if (dmi_busy && dmi_cmderr == 2'd0)
dmi_cmderr <= 2'h3;
end else if (write_dtmcs) begin
// Writing dtmcs.dmireset = 1 clears a sticky error
if (dr_wdata[16])
dmi_cmderr <= 2'd0;
end else if (write_dmi) begin
if (dtm_psel) begin
dmi_busy <= 1'b1;
end else if (dr_wdata[1:0] != 2'd0) begin
// DMI ignored operation, so set sticky busy
if (dmi_cmderr == 2'd0)
dmi_cmderr <= 2'd3;
end
end else if (dmi_busy && dtm_pready) begin
dmi_busy <= 1'b0;
if (dmi_cmderr == 2'd0 && dtm_pslverr)
dmi_cmderr <= 2'd2;
end
end
// DTM logic is in TCK domain, actual DMI + DM is in processor domain
hazard3_apb_async_bridge #(
.W_ADDR (W_ADDR),
.W_DATA (32),
.N_SYNC_STAGES (2)
) inst_hazard3_apb_async_bridge (
.clk_src (tck),
.rst_n_src (trst_n),
.clk_dst (clk_dmi),
.rst_n_dst (rst_n_dmi),
.src_psel (dtm_psel),
.src_penable (dtm_penable),
.src_pwrite (dtm_pwrite),
.src_paddr (dtm_paddr),
.src_pwdata (dtm_pwdata),
.src_prdata (dtm_prdata),
.src_pready (dtm_pready),
.src_pslverr (dtm_pslverr),
.dst_psel (dmi_psel),
.dst_penable (dmi_penable),
.dst_pwrite (dmi_pwrite),
.dst_paddr (dmi_paddr),
.dst_pwdata (dmi_pwdata),
.dst_prdata (dmi_prdata),
.dst_pready (dmi_pready),
.dst_pslverr (dmi_pslverr)
);
// ----------------------------------------------------------------------------
// DR read/write
wire [W_DR_SHIFT-1:0] dtmcs_rdata = {
{W_ADDR{1'b0}},
19'h0,
DTMCS_IDLE_HINT[2:0],
dmi_cmderr,
W_ADDR[5:0], // abits
4'd1 // version
};
wire [W_DR_SHIFT-1:0] dmi_rdata = {
{W_ADDR{1'b0}},
dtm_prdata,
dmi_busy && dmi_cmderr == 2'd0 ? 2'd3 : dmi_cmderr
};
assign dr_rdata = dr_sel_dmi_ndtmcs ? dmi_rdata : dtmcs_rdata;
always @ (posedge tck or negedge trst_n) begin
if (!trst_n) begin
dmihardreset_req <= 1'b0;
end else begin
dmihardreset_req <= write_dtmcs && dr_wdata[17];
end
end
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+162
View File
@@ -0,0 +1,162 @@
/*****************************************************************************\
| Copyright (C) 2021-2025 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Implement a RISC-V JTAG DTM tunnelled through a Xilinx BSCANE2 primitive.
//
// Xilinx allows up to four custom DRs to be added to the FPGA TAP controller.
// A JTAG-DTM only needs two: DTMCS and DMI.
//
// With the correct config, OpenOCD can treat the FPGA TAP as a JTAG DTM and
// access RISC-V debug directly. This allows you to debug internal RISC-V
// cores with the same JTAG interface you use to load the FPGA.
//
// CHAIN_DTMCS and CHAIN_DMI select which JTAG IR values are used to access
// these DRs. Values 1 through 4 correspond to Xilinx USER1 through USER4
// instructions, which have IR values 0x02, 0x03, 0x22, 0x23.
`default_nettype none
module hazard3_xilinx7_jtag_dtm #(
parameter SEL_DTMCS = 3,
parameter SEL_DMI = 4,
parameter DTMCS_IDLE_HINT = 3'd4,
parameter W_PADDR = 9,
parameter ABITS = W_PADDR - 2 // do not modify
) (
// This is synchronous to TCK and asserted for one TCK cycle only
output wire dmihardreset_req,
// Bus clock + reset for Debug Module Interface
input wire clk_dmi,
input wire rst_n_dmi,
// Debug Module Interface (APB)
output wire dmi_psel,
output wire dmi_penable,
output wire dmi_pwrite,
output wire [W_PADDR-1:0] dmi_paddr,
output wire [31:0] dmi_pwdata,
input wire [31:0] dmi_prdata,
input wire dmi_pready,
input wire dmi_pslverr
);
// Signals to/from the Xilinx TAP
wire jtck_unbuf;
wire jtck;
wire jtdo2;
wire jtdo1;
wire jtdi;
wire jshift;
wire jupdate;
wire jcapture;
wire jrst;
wire jrst_n = !jrst;
wire jce2;
wire jce1;
BSCANE2 #(
.JTAG_CHAIN (SEL_DTMCS) // Value for USER command.
) bscan_dtmcs (
.CAPTURE (jcapture), // CAPTURE output from TAP controller.
.DRCK (/* unused */), // Gated TCK output. When SEL is asserted, DRCK toggles when CAPTURE or SHIFT are asserted.
.RESET (jrst), // Reset output for TAP controller.
.RUNTEST (/* unused */), // Output asserted when TAP controller is in Run Test/Idle state.
.SEL (jce1), // USER instruction active output.
.SHIFT (jshift), // SHIFT output from TAP controller.
.TCK (jtck_unbuf), // Test Clock output. Fabric connection to TAP Clock pin.
.TDI (jtdi), // Test Data Input (TDI) output from TAP controller.
.TMS (/* unused */), // Test Mode Select output. Fabric connection to TAP.
.UPDATE (jupdate), // UPDATE output from TAP controller
.TDO (jtdo1) // Test Data Output (TDO) input for USER function.
);
BSCANE2 #(
.JTAG_CHAIN (SEL_DMI)
) bscan_dmi (
.CAPTURE (/* unused */),
.DRCK (/* unused */),
.RESET (/* unused */),
.RUNTEST (/* unused */),
.SEL (jce2),
.SHIFT (/* unused */),
.TCK (/* unused */),
.TDI (/* unused */),
.TMS (/* unused */),
.UPDATE (/* unused */),
.TDO (jtdo2)
);
BUFG bufg_jtck (
.I (jtck_unbuf),
.O (jtck)
);
localparam W_DR_SHIFT = ABITS + 32 + 2;
wire core_dr_wen = jupdate;
wire core_dr_ren = jcapture;
wire core_dr_sel_dmi_ndtmcs = !jce1;
wire dr_shift_en = jshift;
wire [W_DR_SHIFT-1:0] core_dr_wdata;
wire [W_DR_SHIFT-1:0] core_dr_rdata;
reg [W_DR_SHIFT-1:0] dr_shift;
assign core_dr_wdata = dr_shift;
always @ (posedge jtck or negedge jrst_n) begin
if (!jrst_n) begin
dr_shift <= {W_DR_SHIFT{1'b0}};
end else if (core_dr_ren) begin
dr_shift <= core_dr_rdata;
end else if (dr_shift_en) begin
dr_shift <= {jtdi, dr_shift[W_DR_SHIFT-1:1]};
if (!core_dr_sel_dmi_ndtmcs)
dr_shift[31] <= jtdi;
end
end
// We have only a single shifter for the two DRs, so these are tied together:
assign jtdo1 = dr_shift[0];
assign jtdo2 = dr_shift[0];
// The actual DTM is in here:
hazard3_jtag_dtm_core #(
.DTMCS_IDLE_HINT (DTMCS_IDLE_HINT),
.W_ADDR (ABITS)
) inst_hazard3_jtag_dtm_core (
.tck (jtck),
.trst_n (jrst_n),
.clk_dmi (clk_dmi),
.rst_n_dmi (rst_n_dmi),
.dr_wen (core_dr_wen),
.dr_ren (core_dr_ren),
.dr_sel_dmi_ndtmcs (core_dr_sel_dmi_ndtmcs),
.dr_wdata (core_dr_wdata),
.dr_rdata (core_dr_rdata),
.dmihardreset_req (dmihardreset_req),
.dmi_psel (dmi_psel),
.dmi_penable (dmi_penable),
.dmi_pwrite (dmi_pwrite),
.dmi_paddr (dmi_paddr[W_PADDR-1:2]),
.dmi_pwdata (dmi_pwdata),
.dmi_prdata (dmi_prdata),
.dmi_pready (dmi_pready),
.dmi_pslverr (dmi_pslverr)
);
assign dmi_paddr[1:0] = 2'b00;
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+22
View File
@@ -0,0 +1,22 @@
file hazard3_core.v
file hazard3_cpu_1port.v
file hazard3_cpu_2port.v
file arith/hazard3_alu.v
file arith/hazard3_branchcmp.v
file arith/hazard3_mul_fast.v
file arith/hazard3_muldiv_seq.v
file arith/hazard3_onehot_encode.v
file arith/hazard3_onehot_priority.v
file arith/hazard3_onehot_priority_dynamic.v
file arith/hazard3_priority_encode.v
file arith/hazard3_shift_barrel.v
file hazard3_csr.v
file hazard3_decode.v
file hazard3_frontend.v
file hazard3_instr_decompress.v
file hazard3_irq_ctrl.v
file hazard3_pmp.v
file hazard3_power_ctrl.v
file hazard3_regfile_1w2r.v
file hazard3_triggers.v
include .
+259
View File
@@ -0,0 +1,259 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Hazard3 CPU configuration parameters
// To configure Hazard3 you can either edit this file, or set parameters on
// your top-level instantiation, it's up to you. These parameters are all
// plumbed through Hazard3's internal hierarchy to the appropriate places.
// If you add a parameter here, you should add a matching line to
// hazard3_config_inst.vh to propagate the parameter through module
// instantiations.
// ----------------------------------------------------------------------------
// Reset state configuration
// RESET_VECTOR: Address of first instruction executed.
parameter RESET_VECTOR = 32'h00000000,
// MTVEC_INIT: Initial value of trap vector base. Bits clear in MTVEC_WMASK
// will never change from this initial value. Bits set in MTVEC_WMASK can be
// written/set/cleared as normal.
//
// Note that mtvec bits 1:0 do not affect the trap base (as per RISC-V spec).
// Bit 1 is don't care, bit 0 selects the vectoring mode: unvectored if == 0
// (all traps go to mtvec), vectored if == 1 (exceptions go to mtvec, IRQs to
// mtvec + mcause * 4). This means MTVEC_INIT also sets the initial vectoring
// mode.
parameter MTVEC_INIT = 32'h00000000,
// ----------------------------------------------------------------------------
// Standard RISC-V ISA support
// EXTENSION_A: Support for atomic read/modify/write instructions
parameter EXTENSION_A = 1,
// EXTENSION_C: Support for compressed (variable-width) instructions
parameter EXTENSION_C = 1,
// EXTENSION_E: Implement the RV32E base extension rather than RV32I. This
// reduces the number of integer registers from 31 to 15.
parameter EXTENSION_E = 0,
// EXTENSION_M: Support for hardware multiply/divide/modulo instructions
parameter EXTENSION_M = 1,
// EXTENSION_ZBA: Support for Zba address generation instructions
parameter EXTENSION_ZBA = 0,
// EXTENSION_ZBB: Support for Zbb basic bit manipulation instructions
parameter EXTENSION_ZBB = 0,
// EXTENSION_ZBC: Support for Zbc carry-less multiplication instructions
parameter EXTENSION_ZBC = 0,
// EXTENSION_ZBKB: Support for Zbkb basic bit manipulation for cryptography
// Requires: Zbb. (This flag enables instructions in Zbkb which aren't in Zbb.)
parameter EXTENSION_ZBKB = 0,
// EXTENSION_ZBKX: support for Zbkx crossbar permutation instructions
parameter EXTENSION_ZBKX = 0,
// EXTENSION_ZBS: Support for Zbs single-bit manipulation instructions
parameter EXTENSION_ZBS = 0,
// EXTENSION_ZCB: Support for Zcb basic additional compressed instructions
// Requires: EXTENSION_C. (Some Zcb instructions also require Zbb or M.)
// Note Zca is equivalent to C, as we do not support the F extension.
parameter EXTENSION_ZCB = 0,
// EXTENSION_ZCLSD: Support for Zclsd compressed load/store pair instructions
// Requires: EXTENSION_ZILSD, EXTENSION_C.
parameter EXTENSION_ZCLSD = 0,
// EXTENSION_ZCMP: Support for Zcmp push/pop instructions.
// Requires: EXTENSION_C.
parameter EXTENSION_ZCMP = 0,
// EXTENSION_ZIFENCEI: Support for the fence.i instruction
// Optional, since a plain branch/jump will also flush the prefetch queue.
parameter EXTENSION_ZIFENCEI = 0,
// EXTENSION_ZILSD: Support for Zilsd load/store pair instructions
parameter EXTENSION_ZILSD = 0,
// ----------------------------------------------------------------------------
// Custom RISC-V extensions
// EXTENSION_XH3B: Custom bit-extract-multiple instructions for Hazard3
parameter EXTENSION_XH3BEXTM = 0,
// EXTENSION_XH3IRQ: Custom preemptive, prioritised interrupt support. Can be
// disabled if an external interrupt controller (e.g. PLIC) is used. If
// disabled, and NUM_IRQS > 1, the external interrupts are simply OR'd into
// mip.meip.
parameter EXTENSION_XH3IRQ = 0,
// EXTENSION_XH3PMPM: PMPCFGMx CSRs to enforce PMP regions in M-mode without
// locking. Unlike ePMP mseccfg.rlb, locked and unlocked regions can coexist
parameter EXTENSION_XH3PMPM = 0,
// EXTENSION_XH3POWER: Custom power management controls for Hazard3
parameter EXTENSION_XH3POWER = 0,
// ----------------------------------------------------------------------------
// Standard CSR support
// Note the Zicsr extension is implied by any of CSR_M_MANDATORY, CSR_M_TRAP,
// CSR_COUNTER.
// CSR_M_MANDATORY: Bare minimum CSR support e.g. misa. Spec says must = 1 if
// CSRs are present, but I won't tell anyone.
parameter CSR_M_MANDATORY = 1,
// CSR_M_TRAP: Include M-mode trap-handling CSRs, and enable trap support.
parameter CSR_M_TRAP = 1,
// CSR_COUNTER: Include performance counters and Zicntr CSRs
parameter CSR_COUNTER = 0,
// U_MODE: Support the U (user) execution mode. In U mode, the core performs
// unprivileged bus accesses, and software's access to CSRs is restricted.
// Additionally, if the PMP is included, the core may restrict U-mode
// software's access to memory.
// Requires: CSR_M_TRAP.
parameter U_MODE = 0,
// PMP_REGIONS: Number of physical memory protection regions, or 0 for no PMP.
// PMP is more useful if U mode is supported, but this is not a requirement.
parameter PMP_REGIONS = 0,
// PMP_GRAIN: This is the "G" parameter in the privileged spec. Minimum PMP
// region size is 1 << (G + 2) bytes. If G > 0, PMCFG.A can not be set to
// NA4 (will get set to OFF instead). If G > 1, the G - 1 LSBs of pmpaddr are
// read-only-0 when PMPCFG.A is OFF, and read-only-1 when PMPCFG.A is NAPOT.
parameter PMP_GRAIN = 0,
// PMP_MATCH_NAPOT: Enable PMP support for the NAPOT (naturally-aligned
// power-of-two) and NA4 (naturally-aligned four-byte) matching modes. When
// disabled, attempting to select these modes will set the PMP region to OFF.
parameter PMP_MATCH_NAPOT = 1,
// PMP_MATCH_TOR: Enable PMP support for the TOR (top-of-range) matching mode.
// When disabled, attempting to select this mode will set the region to OFF.
parameter PMP_MATCH_TOR = 0,
// PMPADDR_HARDWIRED: If a bit is 1, the corresponding region's pmpaddr and
// pmpcfg registers are read-only. PMP_GRAIN is ignored on hardwired regions.
// It's recommended to make hardwired regions the highest-numbered, so they
// can be overridden by lower-numbered regions.
parameter PMP_HARDWIRED = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){1'b0}},
// PMPADDR_HARDWIRED_ADDR: Values of pmpaddr registers whose PMP_HARDWIRED
// bits are set to 1. Non-hardwired regions reset to all-zeroes.
parameter PMP_HARDWIRED_ADDR = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){32'h0}},
// PMPCFG_RESET_VAL: Values of pmpcfg registers whose PMP_HARDWIRED bits are
// set to 1. Non-hardwired regions reset to all zeroes.
parameter PMP_HARDWIRED_CFG = {(PMP_REGIONS > 0 ? PMP_REGIONS : 1){8'h00}},
// DEBUG_SUPPORT: Support for run/halt and instruction injection from an
// external Debug Module, support for Debug Mode, and Debug Mode CSRs.
// Requires: CSR_M_MANDATORY, CSR_M_TRAP.
parameter DEBUG_SUPPORT = 0,
// BREAKPOINT_TRIGGERS: Number of triggers which support type=2 execute=1
// (but not store/load=1, i.e. not a watchpoint). Requires: DEBUG_SUPPORT
parameter BREAKPOINT_TRIGGERS = 0,
// ----------------------------------------------------------------------------
// External interrupt support
// NUM_IRQS: Number of external IRQs. Minimum 1, maximum 512. Note that if
// EXTENSION_XH3IRQ (Hazard3 interrupt controller) is disabled then multiple
// external interrupts are simply OR'd into mip.meip.
parameter NUM_IRQS = 1,
// IRQ_PRIORITY_BITS: Number of priority bits implemented for each interrupt
// in meipra, if EXTENSION_XH3IRQ is enabled. The number of distinct levels
// is (1 << IRQ_PRIORITY_BITS). Minimum 0, max 4. Note that multiple priority
// levels with a large number of IRQs will have a severe effect on timing.
parameter IRQ_PRIORITY_BITS = 0,
// IRQ_INPUT_BYPASS: disable the input registers on the external interrupts,
// to reduce latency by one cycle. Can be applied on an IRQ-by-IRQ basis.
// Ignored if EXTENSION_XH3IRQ is disabled.
parameter IRQ_INPUT_BYPASS = {(NUM_IRQS > 0 ? NUM_IRQS : 1){1'b0}},
// ----------------------------------------------------------------------------
// ID registers
// JEDEC JEP106-compliant vendor ID, can be left at 0 if "not implemented or
// [...] this is a non-commercial implementation" (RISC-V spec).
// 31:7 is continuation code count, 6:0 is ID. Parity bit is not stored.
parameter MVENDORID_VAL = 32'h0,
// Pointer to configuration structure blob, or all-zeroes. Must be at least
// 4-byte-aligned.
parameter MCONFIGPTR_VAL = 32'h0,
// ----------------------------------------------------------------------------
// Performance/size options
// REDUCED_BYPASS: Remove all forwarding paths except X->X (so back-to-back
// ALU ops can still run at 1 CPI), to save area.
parameter REDUCED_BYPASS = 0,
// MULDIV_UNROLL: Bits per clock for multiply/divide circuit, if present. Must
// be a power of 2.
parameter MULDIV_UNROLL = 1,
// MUL_FAST: Use single-cycle multiply circuit for MUL instructions, retiring
// to stage 3. The sequential multiply/divide circuit is still used for MULH*
parameter MUL_FAST = 0,
// MUL_FASTER: Retire fast multiply results to stage 2 instead of stage 3.
// Throughput is the same, but latency is reduced from 2 cycles to 1 cycle.
// Requires: MUL_FAST.
parameter MUL_FASTER = 0,
// MULH_FAST: extend the fast multiply circuit to also cover MULH*, and remove
// the multiply functionality from the sequential multiply/divide circuit.
// Requires: MUL_FAST
parameter MULH_FAST = 0,
// FAST_BRANCHCMP: Instantiate a separate comparator (eq/lt/ltu) for branch
// comparisons, rather than using the ALU. Improves fetch address delay,
// especially if Zba extension is enabled. Disabling may save area.
parameter FAST_BRANCHCMP = 1,
// RESET_REGFILE: whether to support reset of the general purpose registers.
// There are around 1k bits in the register file, so the reset can be
// disabled e.g. to permit block-RAM inference on FPGA.
parameter RESET_REGFILE = 0,
// BRANCH_PREDICTOR: enable branch prediction. The branch predictor consists
// of a single BTB entry which is allocated on a taken backward branch, and
// cleared on a mispredicted nontaken branch, a fence.i or a trap. Successful
// prediction eliminates the 1-cyle fetch bubble on a taken branch, usually
// making tight loops faster.
parameter BRANCH_PREDICTOR = 0,
// MTVEC_WMASK: Mask of which bits in mtvec are writable. Full writability is
// recommended, because a common idiom in setup code is to set mtvec just
// past code that may trap, as a hardware "try {...} catch" block.
//
// - The vectoring mode can be made fixed by clearing the LSB of MTVEC_WMASK
//
// - In vectored mode, the vector table must be aligned to its size, rounded
// up to a power of two.
parameter MTVEC_WMASK = 32'hfffffffd,
// ----------------------------------------------------------------------------
// Port size parameters (do not modify)
parameter W_ADDR = 32, // Do not modify
parameter W_DATA = 32 // Do not modify
+59
View File
@@ -0,0 +1,59 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Pass-through of parameters defined in hazard3_config.vh, so that these can
// be set at instantiation rather than editing the config file, and will flow
// correctly down through the hierarchy.
.RESET_VECTOR (RESET_VECTOR),
.MTVEC_INIT (MTVEC_INIT),
.EXTENSION_A (EXTENSION_A),
.EXTENSION_C (EXTENSION_C),
.EXTENSION_E (EXTENSION_E),
.EXTENSION_M (EXTENSION_M),
.EXTENSION_ZBA (EXTENSION_ZBA),
.EXTENSION_ZBB (EXTENSION_ZBB),
.EXTENSION_ZBC (EXTENSION_ZBC),
.EXTENSION_ZBKB (EXTENSION_ZBKB),
.EXTENSION_ZBKX (EXTENSION_ZBKX),
.EXTENSION_ZBS (EXTENSION_ZBS),
.EXTENSION_ZCB (EXTENSION_ZCB),
.EXTENSION_ZCLSD (EXTENSION_ZCLSD),
.EXTENSION_ZCMP (EXTENSION_ZCMP),
.EXTENSION_ZIFENCEI (EXTENSION_ZIFENCEI),
.EXTENSION_ZILSD (EXTENSION_ZILSD),
.EXTENSION_XH3BEXTM (EXTENSION_XH3BEXTM),
.EXTENSION_XH3IRQ (EXTENSION_XH3IRQ),
.EXTENSION_XH3PMPM (EXTENSION_XH3PMPM),
.EXTENSION_XH3POWER (EXTENSION_XH3POWER),
.CSR_M_MANDATORY (CSR_M_MANDATORY),
.CSR_M_TRAP (CSR_M_TRAP),
.CSR_COUNTER (CSR_COUNTER),
.U_MODE (U_MODE),
.PMP_REGIONS (PMP_REGIONS),
.PMP_GRAIN (PMP_GRAIN),
.PMP_MATCH_NAPOT (PMP_MATCH_NAPOT),
.PMP_MATCH_TOR (PMP_MATCH_TOR),
.PMP_HARDWIRED (PMP_HARDWIRED),
.PMP_HARDWIRED_ADDR (PMP_HARDWIRED_ADDR),
.PMP_HARDWIRED_CFG (PMP_HARDWIRED_CFG),
.DEBUG_SUPPORT (DEBUG_SUPPORT),
.BREAKPOINT_TRIGGERS (BREAKPOINT_TRIGGERS),
.NUM_IRQS (NUM_IRQS),
.IRQ_PRIORITY_BITS (IRQ_PRIORITY_BITS),
.IRQ_INPUT_BYPASS (IRQ_INPUT_BYPASS),
.MVENDORID_VAL (MVENDORID_VAL),
.MCONFIGPTR_VAL (MCONFIGPTR_VAL),
.REDUCED_BYPASS (REDUCED_BYPASS),
.MULDIV_UNROLL (MULDIV_UNROLL),
.MUL_FAST (MUL_FAST),
.MUL_FASTER (MUL_FASTER),
.MULH_FAST (MULH_FAST),
.FAST_BRANCHCMP (FAST_BRANCHCMP),
.BRANCH_PREDICTOR (BRANCH_PREDICTOR),
.MTVEC_WMASK (MTVEC_WMASK),
.RESET_REGFILE (RESET_REGFILE),
.W_ADDR (W_ADDR),
.W_DATA (W_DATA)
+1590
View File
File diff suppressed because it is too large Load Diff
+343
View File
@@ -0,0 +1,343 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Single-ported top level file for Hazard3 CPU. This file instantiates the
// Hazard3 core, and arbitrates its instruction fetch and load/store signals
// down to a single AHB5 master port.
`ifdef HAZARD3_RVFI_STANDALONE
`include "hazard3_rvfi_standalone_defs.vh"
`endif
`default_nettype none
module hazard3_cpu_1port #(
`include "hazard3_config.vh"
) (
// Global signals
input wire clk,
input wire clk_always_on,
input wire rst_n,
`ifdef RISCV_FORMAL
`RVFI_OUTPUTS ,
`endif
// Power control signals
output wire pwrup_req,
input wire pwrup_ack,
output wire clk_en,
output wire unblock_out,
input wire unblock_in,
// AHB5 Master port
output reg [W_ADDR-1:0] haddr,
output reg hwrite,
output reg [1:0] htrans,
output reg [2:0] hsize,
output wire [2:0] hburst,
output reg [3:0] hprot,
output wire hmastlock,
output reg [7:0] hmaster,
output reg hexcl,
input wire hready,
input wire hresp,
input wire hexokay,
output wire [W_DATA-1:0] hwdata,
input wire [W_DATA-1:0] hrdata,
// Memory ordering signals
output wire fence_i_vld,
output wire fence_d_vld,
input wire fence_rdy,
// Debugger run/halt control
input wire dbg_req_halt,
input wire dbg_req_halt_on_reset,
input wire dbg_req_resume,
output wire dbg_halted,
output wire dbg_running,
// Debugger access to data0 CSR
input wire [W_DATA-1:0] dbg_data0_rdata,
output wire [W_DATA-1:0] dbg_data0_wdata,
output wire dbg_data0_wen,
// Debugger instruction injection
input wire [W_DATA-1:0] dbg_instr_data,
input wire dbg_instr_data_vld,
output wire dbg_instr_data_rdy,
output wire dbg_instr_caught_exception,
output wire dbg_instr_caught_ebreak,
// Optional debug system bus access patch-through
input wire [W_ADDR-1:0] dbg_sbus_addr,
input wire dbg_sbus_write,
input wire [1:0] dbg_sbus_size,
input wire dbg_sbus_vld,
output wire dbg_sbus_rdy,
output wire dbg_sbus_err,
input wire [W_DATA-1:0] dbg_sbus_wdata,
output wire [W_DATA-1:0] dbg_sbus_rdata,
// Identification CSR values
input wire [W_DATA-1:0] mhartid_val,
input wire [3:0] eco_version,
// Level-sensitive interrupt sources
input wire [NUM_IRQS-1:0] irq, // -> mip.meip
input wire soft_irq, // -> mip.msip
input wire timer_irq // -> mip.mtip
);
// ----------------------------------------------------------------------------
// Processor core
// Instruction fetch signals
wire core_aph_req_i;
wire core_aph_panic_i;
wire core_aph_ready_i;
wire core_dph_ready_i;
wire core_dph_err_i;
wire [W_ADDR-1:0] core_haddr_i;
wire [2:0] core_hsize_i;
wire core_priv_i;
wire [W_DATA-1:0] core_rdata_i;
// Load/store signals
wire core_aph_req_d;
wire core_aph_excl_d;
wire core_aph_ready_d;
wire core_dph_ready_d;
wire core_dph_err_d;
wire core_dph_exokay_d;
wire [W_ADDR-1:0] core_haddr_d;
wire [2:0] core_hsize_d;
wire core_priv_d;
wire core_hwrite_d;
wire [W_DATA-1:0] core_wdata_d;
wire [W_DATA-1:0] core_rdata_d;
hazard3_core #(
`include "hazard3_config_inst.vh"
) core (
.clk (clk),
.clk_always_on (clk_always_on),
.rst_n (rst_n),
.pwrup_req (pwrup_req),
.pwrup_ack (pwrup_ack),
.clk_en (clk_en),
.unblock_out (unblock_out),
.unblock_in (unblock_in),
`ifdef RISCV_FORMAL
`RVFI_CONN ,
`endif
.bus_aph_req_i (core_aph_req_i),
.bus_aph_panic_i (core_aph_panic_i),
.bus_aph_ready_i (core_aph_ready_i),
.bus_dph_ready_i (core_dph_ready_i),
.bus_dph_err_i (core_dph_err_i),
.bus_haddr_i (core_haddr_i),
.bus_hsize_i (core_hsize_i),
.bus_priv_i (core_priv_i),
.bus_rdata_i (core_rdata_i),
.bus_aph_req_d (core_aph_req_d),
.bus_aph_excl_d (core_aph_excl_d),
.bus_aph_ready_d (core_aph_ready_d),
.bus_dph_ready_d (core_dph_ready_d),
.bus_dph_err_d (core_dph_err_d),
.bus_dph_exokay_d (core_dph_exokay_d),
.bus_haddr_d (core_haddr_d),
.bus_hsize_d (core_hsize_d),
.bus_priv_d (core_priv_d),
.bus_hwrite_d (core_hwrite_d),
.bus_wdata_d (core_wdata_d),
.bus_rdata_d (core_rdata_d),
.fence_i_vld (fence_i_vld),
.fence_d_vld (fence_d_vld),
.fence_rdy (fence_rdy),
.dbg_req_halt (dbg_req_halt),
.dbg_req_halt_on_reset (dbg_req_halt_on_reset),
.dbg_req_resume (dbg_req_resume),
.dbg_halted (dbg_halted),
.dbg_running (dbg_running),
.dbg_data0_rdata (dbg_data0_rdata),
.dbg_data0_wdata (dbg_data0_wdata),
.dbg_data0_wen (dbg_data0_wen),
.dbg_instr_data (dbg_instr_data),
.dbg_instr_data_vld (dbg_instr_data_vld),
.dbg_instr_data_rdy (dbg_instr_data_rdy),
.dbg_instr_caught_exception (dbg_instr_caught_exception),
.dbg_instr_caught_ebreak (dbg_instr_caught_ebreak),
.mhartid_val (mhartid_val),
.eco_version (eco_version),
.irq (irq),
.soft_irq (soft_irq),
.timer_irq (timer_irq)
);
// ----------------------------------------------------------------------------
// Arbitration state machine
wire bus_gnt_i;
wire bus_gnt_d;
wire bus_gnt_s;
reg bus_hold_aph;
reg [2:0] bus_gnt_ids_prev;
// Note use of clk_always_on: SBA may use this arbiter to access the bus
// whilst the core is asleep.
always @ (posedge clk_always_on or negedge rst_n) begin
if (!rst_n) begin
bus_hold_aph <= 1'b0;
bus_gnt_ids_prev <= 3'h0;
end else begin
bus_hold_aph <= htrans[1] && !hready && !hresp;
bus_gnt_ids_prev <= {bus_gnt_i, bus_gnt_d, bus_gnt_s};
end
end
// Debug SBA access is lower priority than load/store, but higher than
// instruction fetch. This isn't ideal, but in a tight loop the core may be
// performing an instruction fetch or load/store on every single cycle, and
// this is a simple way to guarantee eventual success of debugger accesses. A
// more complex way would be to add a "panic timer" to boost a stalled sbus
// access over an instruction fetch.
// Note that, often, the sbus will be disconnected: it doesn't provide any
// increase in debugger bus throughput compared with the program buffer and
// autoexec. It's useful for "minimally intrusive" debug bus access(i.e. less
// intrusive than halting the core and resuming it) e.g. for Segger RTT.
reg bus_active_dph_s;
assign {bus_gnt_i, bus_gnt_d, bus_gnt_s} =
bus_hold_aph ? bus_gnt_ids_prev :
core_aph_panic_i ? 3'b100 :
core_aph_req_d ? 3'b010 :
dbg_sbus_vld && !bus_active_dph_s ? 3'b001 :
core_aph_req_i ? 3'b100 :
3'b000 ;
reg bus_active_dph_i;
reg bus_active_dph_d;
always @ (posedge clk_always_on or negedge rst_n) begin
if (!rst_n) begin
bus_active_dph_i <= 1'b0;
bus_active_dph_d <= 1'b0;
bus_active_dph_s <= 1'b0;
end else if (hready) begin
bus_active_dph_i <= bus_gnt_i;
bus_active_dph_d <= bus_gnt_d;
bus_active_dph_s <= bus_gnt_s;
end
end
// ----------------------------------------------------------------------------
// Address phase request muxing
localparam HTRANS_IDLE = 2'b00;
localparam HTRANS_NSEQ = 2'b10;
wire [3:0] hprot_data = {
2'b00, // Noncacheable/nonbufferable
core_priv_d, // Privileged or Normal as per core state
1'b1 // Data access
};
wire [3:0] hprot_instr = {
2'b00, // Noncacheable/nonbufferable
core_priv_i, // Privileged or Normal as per core state
1'b0 // Instruction access
};
wire [3:0] hprot_sbus = {
2'b00, // Noncacheable/nonbufferable
1'b1, // Always privileged
1'b1 // Data access
};
assign hburst = 3'b000; // HBURST_SINGLE
assign hmastlock = 1'b0;
always @ (*) begin
if (bus_gnt_s) begin
htrans = HTRANS_NSEQ;
hexcl = 1'b0;
haddr = dbg_sbus_addr;
hsize = {1'b0, dbg_sbus_size};
hwrite = dbg_sbus_write;
hprot = hprot_sbus;
hmaster = 8'h01;
end else if (bus_gnt_d) begin
htrans = HTRANS_NSEQ;
hexcl = core_aph_excl_d;
haddr = core_haddr_d;
hsize = core_hsize_d;
hwrite = core_hwrite_d;
hprot = hprot_data;
hmaster = 8'h00;
end else if (bus_gnt_i) begin
htrans = HTRANS_NSEQ;
hexcl = 1'b0;
haddr = core_haddr_i;
hsize = core_hsize_i;
hwrite = 1'b0;
hprot = hprot_instr;
hmaster = 8'h00;
end else begin
htrans = HTRANS_IDLE;
hexcl = 1'b0;
haddr = {W_ADDR{1'b0}};
hsize = 3'h0;
hwrite = 1'b0;
hprot = 4'h0;
hmaster = 8'h00;
end
end
assign hwdata = bus_active_dph_s ? dbg_sbus_wdata : core_wdata_d;
// ----------------------------------------------------------------------------
// Response routing
// Data buses directly connected
assign core_rdata_d = hrdata;
assign core_rdata_i = hrdata;
assign dbg_sbus_rdata = hrdata;
// Handhshake based on grant and bus stall
assign core_aph_ready_i = hready && bus_gnt_i;
assign core_dph_ready_i = bus_active_dph_i && hready;
assign core_dph_err_i = bus_active_dph_i && hresp;
// D-side errors are reported even when not ready, so that the core can make
// use of the two-phase error response to cleanly squash a second load/store
// chasing the faulting one down the pipeline.
assign core_aph_ready_d = hready && bus_gnt_d;
assign core_dph_ready_d = bus_active_dph_d && hready;
assign core_dph_err_d = bus_active_dph_d && hresp;
assign core_dph_exokay_d = bus_active_dph_d && hexokay;
assign dbg_sbus_err = bus_active_dph_s && hresp;
assign dbg_sbus_rdy = bus_active_dph_s && hready;
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+327
View File
@@ -0,0 +1,327 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Dual-ported top level file for Hazard3 CPU. This file instantiates the
// Hazard3 core, and interfaces its instruction fetch and load/store signals
// to a pair of AHB5 master ports.
`ifdef HAZARD3_RVFI_STANDALONE
`include "hazard3_rvfi_standalone_defs.vh"
`endif
`default_nettype none
module hazard3_cpu_2port #(
`include "hazard3_config.vh"
) (
// Global signals
input wire clk,
input wire clk_always_on,
input wire rst_n,
// Power control signals
output wire pwrup_req,
input wire pwrup_ack,
output wire clk_en,
output wire unblock_out,
input wire unblock_in,
`ifdef RISCV_FORMAL
`RVFI_OUTPUTS ,
`endif
// Instruction fetch port
output wire [W_ADDR-1:0] i_haddr,
output wire i_hwrite,
output wire [1:0] i_htrans,
output wire [2:0] i_hsize,
output wire [2:0] i_hburst,
output wire [3:0] i_hprot,
output wire i_hmastlock,
output wire [7:0] i_hmaster,
input wire i_hready,
input wire i_hresp,
output wire [W_DATA-1:0] i_hwdata,
input wire [W_DATA-1:0] i_hrdata,
// Load/store port
output wire [W_ADDR-1:0] d_haddr,
output wire d_hwrite,
output wire [1:0] d_htrans,
output wire [2:0] d_hsize,
output wire [2:0] d_hburst,
output wire [3:0] d_hprot,
output wire d_hmastlock,
output wire [7:0] d_hmaster,
output wire d_hexcl,
input wire d_hready,
input wire d_hresp,
input wire d_hexokay,
output wire [W_DATA-1:0] d_hwdata,
input wire [W_DATA-1:0] d_hrdata,
// Memory ordering signals
output wire fence_i_vld,
output wire fence_d_vld,
input wire fence_rdy,
// Debugger run/halt control
input wire dbg_req_halt,
input wire dbg_req_halt_on_reset,
input wire dbg_req_resume,
output wire dbg_halted,
output wire dbg_running,
// Debugger access to data0 CSR
input wire [W_DATA-1:0] dbg_data0_rdata,
output wire [W_DATA-1:0] dbg_data0_wdata,
output wire dbg_data0_wen,
// Debugger instruction injection
input wire [W_DATA-1:0] dbg_instr_data,
input wire dbg_instr_data_vld,
output wire dbg_instr_data_rdy,
output wire dbg_instr_caught_exception,
output wire dbg_instr_caught_ebreak,
// Optional debug system bus access patch-through
input wire [W_ADDR-1:0] dbg_sbus_addr,
input wire dbg_sbus_write,
input wire [1:0] dbg_sbus_size,
input wire dbg_sbus_vld,
output wire dbg_sbus_rdy,
output wire dbg_sbus_err,
input wire [W_DATA-1:0] dbg_sbus_wdata,
output wire [W_DATA-1:0] dbg_sbus_rdata,
// Identification CSR values
input wire [W_DATA-1:0] mhartid_val,
input wire [3:0] eco_version,
// Level-sensitive interrupt sources
input wire [NUM_IRQS-1:0] irq, // -> mip.meip
input wire soft_irq, // -> mip.msip
input wire timer_irq // -> mip.mtip
);
// ----------------------------------------------------------------------------
// Processor core
// Instruction fetch signals
wire core_aph_req_i;
wire core_aph_ready_i;
wire core_dph_ready_i;
wire core_dph_err_i;
wire [W_ADDR-1:0] core_haddr_i;
wire [2:0] core_hsize_i;
wire core_priv_i;
wire [W_DATA-1:0] core_rdata_i;
// Load/store signals
wire core_aph_req_d;
wire core_aph_excl_d;
wire core_aph_ready_d;
wire core_dph_ready_d;
wire core_dph_err_d;
wire core_dph_exokay_d;
wire [W_ADDR-1:0] core_haddr_d;
wire [2:0] core_hsize_d;
wire core_priv_d;
wire core_hwrite_d;
wire [W_DATA-1:0] core_wdata_d;
wire [W_DATA-1:0] core_rdata_d;
hazard3_core #(
`include "hazard3_config_inst.vh"
) core (
.clk (clk),
.clk_always_on (clk_always_on),
.rst_n (rst_n),
.pwrup_req (pwrup_req),
.pwrup_ack (pwrup_ack),
.clk_en (clk_en),
.unblock_out (unblock_out),
.unblock_in (unblock_in),
`ifdef RISCV_FORMAL
`RVFI_CONN ,
`endif
.bus_aph_req_i (core_aph_req_i),
.bus_aph_panic_i (/* unused for 2port */),
.bus_aph_ready_i (core_aph_ready_i),
.bus_dph_ready_i (core_dph_ready_i),
.bus_dph_err_i (core_dph_err_i),
.bus_haddr_i (core_haddr_i),
.bus_hsize_i (core_hsize_i),
.bus_priv_i (core_priv_i),
.bus_rdata_i (core_rdata_i),
.bus_aph_req_d (core_aph_req_d),
.bus_aph_excl_d (core_aph_excl_d),
.bus_aph_ready_d (core_aph_ready_d),
.bus_dph_ready_d (core_dph_ready_d),
.bus_dph_err_d (core_dph_err_d),
.bus_dph_exokay_d (core_dph_exokay_d),
.bus_haddr_d (core_haddr_d),
.bus_hsize_d (core_hsize_d),
.bus_priv_d (core_priv_d),
.bus_hwrite_d (core_hwrite_d),
.bus_wdata_d (core_wdata_d),
.bus_rdata_d (core_rdata_d),
.fence_i_vld (fence_i_vld),
.fence_d_vld (fence_d_vld),
.fence_rdy (fence_rdy),
.dbg_req_halt (dbg_req_halt),
.dbg_req_halt_on_reset (dbg_req_halt_on_reset),
.dbg_req_resume (dbg_req_resume),
.dbg_halted (dbg_halted),
.dbg_running (dbg_running),
.dbg_data0_rdata (dbg_data0_rdata),
.dbg_data0_wdata (dbg_data0_wdata),
.dbg_data0_wen (dbg_data0_wen),
.dbg_instr_data (dbg_instr_data),
.dbg_instr_data_vld (dbg_instr_data_vld),
.dbg_instr_data_rdy (dbg_instr_data_rdy),
.dbg_instr_caught_exception (dbg_instr_caught_exception),
.dbg_instr_caught_ebreak (dbg_instr_caught_ebreak),
.mhartid_val (mhartid_val),
.eco_version (eco_version),
.irq (irq),
.soft_irq (soft_irq),
.timer_irq (timer_irq)
);
// ----------------------------------------------------------------------------
// Instruction port
localparam HTRANS_IDLE = 2'b00;
localparam HTRANS_NSEQ = 2'b10;
assign i_haddr = core_haddr_i;
assign i_htrans = core_aph_req_i ? HTRANS_NSEQ : HTRANS_IDLE;
assign i_hsize = core_hsize_i;
reg dphase_active_i;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
dphase_active_i <= 1'b0;
end else if (i_hready) begin
dphase_active_i <= core_aph_req_i;
end
end
`ifdef HAZARD3_ASSERTIONS
// Wake->sleep transition must wait for outstanding instruction fetches to
// complete, in particular because the arbiter clock will stop
always @ (posedge clk) if (!rst_n) assert(clk_en || !(core_aph_req_i || dphase_active_i));
`endif
assign core_aph_ready_i = i_hready && core_aph_req_i;
assign core_dph_ready_i = i_hready && dphase_active_i;
assign core_dph_err_i = i_hready && dphase_active_i && i_hresp;
assign core_rdata_i = i_hrdata;
assign i_hwrite = 1'b0;
assign i_hburst = 3'h0;
assign i_hmastlock = 1'b0;
assign i_hmaster = 8'h00;
assign i_hwdata = {W_DATA{1'b0}};
assign i_hprot = {
2'b00, // Noncacheable/nonbufferable
core_priv_i, // Privileged or Normal as per core state
1'b0 // Instruction access
};
// ----------------------------------------------------------------------------
// Load/store port
// The debug module has optional System Bus Access support, which can be muxed
// into the processor's D port here (or connected to a standalone AHB shim).
// This confers absolutely no advantage for debugger bus throughput, but
// allows the debugger to access the bus with minimal disturbance to the
// processor.
wire bus_gnt_d;
wire bus_gnt_s;
reg bus_hold_aph;
reg [1:0] bus_gnt_ds_prev;
reg bus_active_dph_d;
reg bus_active_dph_s;
// clk_always_on is used because SBA may access the bus through this arbiter
// whilst the core is asleep (same is not true for I-side interface)
always @ (posedge clk_always_on or negedge rst_n) begin
if (!rst_n) begin
bus_hold_aph <= 1'b0;
bus_gnt_ds_prev <= 2'h0;
end else begin
bus_hold_aph <= d_htrans[1] && !d_hready && !d_hresp;
bus_gnt_ds_prev <= {bus_gnt_d, bus_gnt_s};
end
end
assign {bus_gnt_d, bus_gnt_s} =
bus_hold_aph ? bus_gnt_ds_prev :
core_aph_req_d ? 2'b10 :
dbg_sbus_vld && !bus_active_dph_s ? 2'b01 :
2'b00 ;
always @ (posedge clk_always_on or negedge rst_n) begin
if (!rst_n) begin
bus_active_dph_d <= 1'b0;
bus_active_dph_s <= 1'b0;
end else if (d_hready) begin
bus_active_dph_d <= bus_gnt_d;
bus_active_dph_s <= bus_gnt_s;
end
end
assign d_htrans = bus_gnt_d || bus_gnt_s ? HTRANS_NSEQ : HTRANS_IDLE;
assign d_haddr = bus_gnt_s ? dbg_sbus_addr : core_haddr_d;
assign d_hwrite = bus_gnt_s ? dbg_sbus_write : core_hwrite_d;
assign d_hsize = bus_gnt_s ? {1'b0, dbg_sbus_size} : core_hsize_d;
assign d_hexcl = bus_gnt_s ? 1'b0 : core_aph_excl_d;
assign d_hprot = {
2'b00, // Noncacheable/nonbufferable
bus_gnt_s || core_priv_d, // Privileged or Normal as per core state
1'b1 // Data access
};
assign d_hwdata = bus_active_dph_s ? dbg_sbus_wdata : core_wdata_d;
// D-side errors are reported even when not ready, so that the core can make
// use of the two-phase error response to cleanly squash a second load/store
// chasing the faulting one down the pipeline.
assign core_aph_ready_d = d_hready && bus_gnt_d;
assign core_dph_ready_d = bus_active_dph_d && d_hready;
assign core_dph_err_d = bus_active_dph_d && d_hresp;
assign core_dph_exokay_d = bus_active_dph_d && d_hexokay;
assign core_rdata_d = d_hrdata;
assign dbg_sbus_err = bus_active_dph_s && d_hresp;
assign dbg_sbus_rdy = bus_active_dph_s && d_hready;
assign dbg_sbus_rdata = d_hrdata;
assign d_hburst = 3'h0;
assign d_hmastlock = 1'b0;
assign d_hmaster = bus_gnt_s ? 8'h01 : 8'h00;
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+1642
View File
File diff suppressed because it is too large Load Diff
+204
View File
@@ -0,0 +1,204 @@
/*****************************************************************************\
| Copyright (C) 2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// List of addresses for CSRs implemented by Hazard3, including custom CSRs.
// ----------------------------------------------------------------------------
// M-mode CSRs
// Machine Information Registers (RO)
localparam MVENDORID = 12'hf11; // Vendor ID.
localparam MARCHID = 12'hf12; // Architecture ID.
localparam MIMPID = 12'hf13; // Implementation ID.
localparam MHARTID = 12'hf14; // Hardware thread ID.
localparam MCONFIGPTR = 12'hf15; // Pointer to configuration data structure.
// Machine Trap Setup (RW)
localparam MSTATUS = 12'h300; // Machine status register.
localparam MSTATUSH = 12'h310; // As of priv-1.12 this must be present even if tied 0.
localparam MISA = 12'h301; // ISA and extensions
localparam MEDELEG = 12'h302; // Machine exception delegation register.
localparam MIDELEG = 12'h303; // Machine interrupt delegation register.
localparam MIE = 12'h304; // Machine interrupt-enable register.
localparam MTVEC = 12'h305; // Machine trap-handler base address.
localparam MCOUNTEREN = 12'h306; // Machine counter enable.
// Machine Trap Handling (RW)
localparam MSCRATCH = 12'h340; // Scratch register for machine trap handlers.
localparam MEPC = 12'h341; // Machine exception program counter.
localparam MCAUSE = 12'h342; // Machine trap cause.
localparam MTVAL = 12'h343; // Machine bad address or instruction.
localparam MIP = 12'h344; // Machine interrupt pending.
// Machine Memory Protection (RW)
localparam PMPCFG0 = 12'h3a0; // Physical memory protection configuration.
localparam PMPCFG1 = 12'h3a1; // Physical memory protection configuration, RV32 only.
localparam PMPCFG2 = 12'h3a2; // Physical memory protection configuration.
localparam PMPCFG3 = 12'h3a3; // Physical memory protection configuration, RV32 only.
localparam PMPADDR0 = 12'h3b0; // Physical memory protection address register.
localparam PMPADDR1 = 12'h3b1; // ...
localparam PMPADDR2 = 12'h3b2;
localparam PMPADDR3 = 12'h3b3;
localparam PMPADDR4 = 12'h3b4;
localparam PMPADDR5 = 12'h3b5;
localparam PMPADDR6 = 12'h3b6;
localparam PMPADDR7 = 12'h3b7;
localparam PMPADDR8 = 12'h3b8;
localparam PMPADDR9 = 12'h3b9;
localparam PMPADDR10 = 12'h3ba;
localparam PMPADDR11 = 12'h3bb;
localparam PMPADDR12 = 12'h3bc;
localparam PMPADDR13 = 12'h3bd;
localparam PMPADDR14 = 12'h3be;
localparam PMPADDR15 = 12'h3bf;
localparam MSECCFG = 12'h747;
localparam MSECCFGH = 12'h757;
// Performance counters (RW)
localparam MCYCLE = 12'hb00; // Raw cycles since start of day
localparam MINSTRET = 12'hb02; // Instruction retire count since start of day
localparam MHPMCOUNTER3 = 12'hb03; // WARL (we tie to 0)
localparam MHPMCOUNTER4 = 12'hb04; // WARL (we tie to 0)
localparam MHPMCOUNTER5 = 12'hb05; // WARL (we tie to 0)
localparam MHPMCOUNTER6 = 12'hb06; // WARL (we tie to 0)
localparam MHPMCOUNTER7 = 12'hb07; // WARL (we tie to 0)
localparam MHPMCOUNTER8 = 12'hb08; // WARL (we tie to 0)
localparam MHPMCOUNTER9 = 12'hb09; // WARL (we tie to 0)
localparam MHPMCOUNTER10 = 12'hb0a; // WARL (we tie to 0)
localparam MHPMCOUNTER11 = 12'hb0b; // WARL (we tie to 0)
localparam MHPMCOUNTER12 = 12'hb0c; // WARL (we tie to 0)
localparam MHPMCOUNTER13 = 12'hb0d; // WARL (we tie to 0)
localparam MHPMCOUNTER14 = 12'hb0e; // WARL (we tie to 0)
localparam MHPMCOUNTER15 = 12'hb0f; // WARL (we tie to 0)
localparam MHPMCOUNTER16 = 12'hb10; // WARL (we tie to 0)
localparam MHPMCOUNTER17 = 12'hb11; // WARL (we tie to 0)
localparam MHPMCOUNTER18 = 12'hb12; // WARL (we tie to 0)
localparam MHPMCOUNTER19 = 12'hb13; // WARL (we tie to 0)
localparam MHPMCOUNTER20 = 12'hb14; // WARL (we tie to 0)
localparam MHPMCOUNTER21 = 12'hb15; // WARL (we tie to 0)
localparam MHPMCOUNTER22 = 12'hb16; // WARL (we tie to 0)
localparam MHPMCOUNTER23 = 12'hb17; // WARL (we tie to 0)
localparam MHPMCOUNTER24 = 12'hb18; // WARL (we tie to 0)
localparam MHPMCOUNTER25 = 12'hb19; // WARL (we tie to 0)
localparam MHPMCOUNTER26 = 12'hb1a; // WARL (we tie to 0)
localparam MHPMCOUNTER27 = 12'hb1b; // WARL (we tie to 0)
localparam MHPMCOUNTER28 = 12'hb1c; // WARL (we tie to 0)
localparam MHPMCOUNTER29 = 12'hb1d; // WARL (we tie to 0)
localparam MHPMCOUNTER30 = 12'hb1e; // WARL (we tie to 0)
localparam MHPMCOUNTER31 = 12'hb1f; // WARL (we tie to 0)
localparam MCYCLEH = 12'hb80; // High halves of each counter
localparam MINSTRETH = 12'hb82;
localparam MHPMCOUNTER3H = 12'hb83;
localparam MHPMCOUNTER4H = 12'hb84;
localparam MHPMCOUNTER5H = 12'hb85;
localparam MHPMCOUNTER6H = 12'hb86;
localparam MHPMCOUNTER7H = 12'hb87;
localparam MHPMCOUNTER8H = 12'hb88;
localparam MHPMCOUNTER9H = 12'hb89;
localparam MHPMCOUNTER10H = 12'hb8a;
localparam MHPMCOUNTER11H = 12'hb8b;
localparam MHPMCOUNTER12H = 12'hb8c;
localparam MHPMCOUNTER13H = 12'hb8d;
localparam MHPMCOUNTER14H = 12'hb8e;
localparam MHPMCOUNTER15H = 12'hb8f;
localparam MHPMCOUNTER16H = 12'hb90;
localparam MHPMCOUNTER17H = 12'hb91;
localparam MHPMCOUNTER18H = 12'hb92;
localparam MHPMCOUNTER19H = 12'hb93;
localparam MHPMCOUNTER20H = 12'hb94;
localparam MHPMCOUNTER21H = 12'hb95;
localparam MHPMCOUNTER22H = 12'hb96;
localparam MHPMCOUNTER23H = 12'hb97;
localparam MHPMCOUNTER24H = 12'hb98;
localparam MHPMCOUNTER25H = 12'hb99;
localparam MHPMCOUNTER26H = 12'hb9a;
localparam MHPMCOUNTER27H = 12'hb9b;
localparam MHPMCOUNTER28H = 12'hb9c;
localparam MHPMCOUNTER29H = 12'hb9d;
localparam MHPMCOUNTER30H = 12'hb9e;
localparam MHPMCOUNTER31H = 12'hb9f;
localparam MCOUNTINHIBIT = 12'h320; // Count inhibit register for mcycle/minstret
localparam MHPMEVENT3 = 12'h323; // WARL (we tie to 0)
localparam MHPMEVENT4 = 12'h324; // WARL (we tie to 0)
localparam MHPMEVENT5 = 12'h325; // WARL (we tie to 0)
localparam MHPMEVENT6 = 12'h326; // WARL (we tie to 0)
localparam MHPMEVENT7 = 12'h327; // WARL (we tie to 0)
localparam MHPMEVENT8 = 12'h328; // WARL (we tie to 0)
localparam MHPMEVENT9 = 12'h329; // WARL (we tie to 0)
localparam MHPMEVENT10 = 12'h32a; // WARL (we tie to 0)
localparam MHPMEVENT11 = 12'h32b; // WARL (we tie to 0)
localparam MHPMEVENT12 = 12'h32c; // WARL (we tie to 0)
localparam MHPMEVENT13 = 12'h32d; // WARL (we tie to 0)
localparam MHPMEVENT14 = 12'h32e; // WARL (we tie to 0)
localparam MHPMEVENT15 = 12'h32f; // WARL (we tie to 0)
localparam MHPMEVENT16 = 12'h330; // WARL (we tie to 0)
localparam MHPMEVENT17 = 12'h331; // WARL (we tie to 0)
localparam MHPMEVENT18 = 12'h332; // WARL (we tie to 0)
localparam MHPMEVENT19 = 12'h333; // WARL (we tie to 0)
localparam MHPMEVENT20 = 12'h334; // WARL (we tie to 0)
localparam MHPMEVENT21 = 12'h335; // WARL (we tie to 0)
localparam MHPMEVENT22 = 12'h336; // WARL (we tie to 0)
localparam MHPMEVENT23 = 12'h337; // WARL (we tie to 0)
localparam MHPMEVENT24 = 12'h338; // WARL (we tie to 0)
localparam MHPMEVENT25 = 12'h339; // WARL (we tie to 0)
localparam MHPMEVENT26 = 12'h33a; // WARL (we tie to 0)
localparam MHPMEVENT27 = 12'h33b; // WARL (we tie to 0)
localparam MHPMEVENT28 = 12'h33c; // WARL (we tie to 0)
localparam MHPMEVENT29 = 12'h33d; // WARL (we tie to 0)
localparam MHPMEVENT30 = 12'h33e; // WARL (we tie to 0)
localparam MHPMEVENT31 = 12'h33f; // WARL (we tie to 0)
// Other standard M-mode CSRs:
localparam MENVCFG = 12'h30a;
localparam MENVCFGH = 12'h31a;
// Custom M-mode CSRs:
localparam PMPCFGM0 = 12'hbd0; // Make PMP regions M-mode without locking
// bd1 // (reserved for >32 regions)
localparam MEIEA = 12'hbe0; // External interrupt pending array
localparam MEIPA = 12'hbe1; // External interrupt enable array
localparam MEIFA = 12'hbe2; // External interrupt force array
localparam MEIPRA = 12'hbe3; // External interrupt priority array
localparam MEINEXT = 12'hbe4; // Next external interrupt
localparam MEICONTEXT = 12'hbe5; // External interrupt context register
localparam MSLEEP = 12'hbf0; // M-mode sleep control register
localparam H3MISA = 12'hbf1; // Hazard3 M-mode ISA identification register
// ----------------------------------------------------------------------------
// U-mode CSRs
// Read-only aliases of M-mode counter CSRs:
localparam CYCLE = 12'hc00;
localparam TIME = 12'hc01;
localparam INSTRET = 12'hc02;
localparam CYCLEH = 12'hc80;
localparam TIMEH = 12'hc81;
localparam INSTRETH = 12'hc82;
// Custom U-mode CSRs
localparam SLEEP = 12'h8f0; // U-mode subset of M-mode sleep control
// ----------------------------------------------------------------------------
// Trigger Module
localparam TSELECT = 12'h7a0;
localparam TDATA1 = 12'h7a1;
localparam TDATA2 = 12'h7a2;
localparam TDATA3 = 12'h7a3;
localparam TINFO = 12'h7a4;
localparam TCONTROL = 12'h7a5;
localparam MCONTEXT = 12'h7a8;
// ----------------------------------------------------------------------------
// D-mode CSRs
localparam DCSR = 12'h7b0;
localparam DPC = 12'h7b1;
localparam DMDATA0 = 12'hbff; // Custom read/write
+594
View File
@@ -0,0 +1,594 @@
/*****************************************************************************\
| Copyright (C) 2021-2023 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
`default_nettype none
module hazard3_decode #(
`include "hazard3_config.vh"
,
`include "hazard3_width_const.vh"
) (
input wire clk,
input wire rst_n,
input wire [31:0] fd_cir,
input wire [1:0] fd_cir_err,
input wire [1:0] fd_cir_predbranch,
input wire [1:0] fd_cir_vld,
input wire fd_cir_is_32bit,
input wire fd_cir_invalid_16bit,
input wire fd_cir_is_uop,
input wire fd_cir_uop_nonfinal,
input wire fd_cir_uop_no_pc_update,
input wire fd_cir_uop_atomic,
output wire [1:0] df_cir_use,
output wire df_cir_flush_behind,
output wire df_uop_stall,
output wire df_uop_clear,
output wire df_lspair_phase_next,
output wire [W_ADDR-1:0] d_pc,
input wire debug_mode,
input wire m_mode,
input wire trap_wfi,
input wire [W_ADDR-1:0] debug_dpc_wdata,
input wire debug_dpc_wen,
output wire [W_ADDR-1:0] debug_dpc_rdata,
output wire d_starved,
input wire x_stall,
input wire f_jump_now,
input wire [W_ADDR-1:0] f_jump_target,
input wire x_jump_not_except,
input wire [W_ADDR-1:0] d_btb_target_addr,
output reg [W_DATA-1:0] d_imm,
output reg [W_REGADDR-1:0] d_rs1,
output reg [W_REGADDR-1:0] d_rs2,
output reg [W_REGADDR-1:0] d_rd,
output reg [2:0] d_funct3_32b,
output reg [6:0] d_funct7_32b,
output reg [W_ALUSRC-1:0] d_alusrc_a,
output reg [W_ALUSRC-1:0] d_alusrc_b,
output reg [W_ALUOP-1:0] d_aluop,
output reg [W_MEMOP-1:0] d_memop,
output reg [W_MULOP-1:0] d_mulop,
output reg d_csr_ren,
output reg d_csr_wen,
output reg [1:0] d_csr_wtype,
output reg d_csr_w_imm,
output reg [W_BCOND-1:0] d_branchcond,
output reg [W_ADDR-1:0] d_addr_offs,
output reg d_addr_is_regoffs,
output reg [W_EXCEPT-1:0] d_except,
output reg d_sleep_wfi,
output reg d_sleep_block,
output reg d_sleep_unblock,
output wire d_no_pc_increment,
output wire d_uninterruptible,
output wire [W_ADDR-1:0] d_lspair_offset,
output reg d_fence_i,
output reg d_fence_d
);
`include "rv_opcodes.vh"
`include "hazard3_ops.vh"
localparam HAVE_CSR = CSR_M_MANDATORY || CSR_M_TRAP || CSR_COUNTER;
// ----------------------------------------------------------------------------
wire [31:0] d_instr = fd_cir | {
30'd0, {2{~|EXTENSION_C}}
};
reg d_invalid_32bit;
wire d_invalid = fd_cir_invalid_16bit || d_invalid_32bit;
assign d_uninterruptible = |EXTENSION_ZCMP && fd_cir_uop_atomic;
`ifdef HAZARD3_ASSERTIONS
always @ (posedge clk) if (rst_n) begin
assert(!(d_invalid && fd_cir_is_uop));
assert(!(d_invalid && fd_cir_uop_atomic));
end
`endif
wire d_lspair_nonfinal;
// Signal to null the mepc offset when taking an exception on this
// instruction (because uops in a sequence *which can except*, so excluding
// the final sp adjust on popret/popretz, will all have the same PC as the
// next uop, which will be in stage 2 when they take their exception)
assign d_no_pc_increment = fd_cir_uop_nonfinal || d_lspair_nonfinal;
assign df_uop_stall = x_stall || d_starved;
// Note !df_cir_flush_behind because the jump in cm.popret/popretz is the
// *penultimate* instruction: we execute the stack adjustment in the fetch
// bubble to save a cycle, still need to finish the uop sequence.
//
// The sp adjust cannot generate an exception (it's an `add` with the same
// PMP.X and breakpoint comparison results as earlier uops) and interrupts are
// suppressed for this part of the sequence.
assign df_uop_clear = f_jump_now && !df_cir_flush_behind;
// Decode various immediate formats
wire [31:0] d_imm_i = {{21{d_instr[31]}}, d_instr[30:20]};
wire [31:0] d_imm_s = {{21{d_instr[31]}}, d_instr[30:25], d_instr[11:7]};
wire [31:0] d_imm_b = {{20{d_instr[31]}}, d_instr[7], d_instr[30:25], d_instr[11:8], 1'b0};
wire [31:0] d_imm_u = {d_instr[31:12], {12{1'b0}}};
wire [31:0] d_imm_j = {{12{d_instr[31]}}, d_instr[19:12], d_instr[20], d_instr[30:21], 1'b0};
// ----------------------------------------------------------------------------
// PC/CIR control
// Must not flag bus error for a valid 16-bit instruction *followed by* an
// error, because instruction fetch errors are speculative, and can be
// flushed by e.g. a branch instruction. Note the 16 LSBs must be valid for
// us to know an instruction's size.
wire d_except_instr_bus_fault = fd_cir_vld > 2'd0 && fd_cir_err[0] ||
fd_cir_vld > 2'd1 && fd_cir_is_32bit && fd_cir_err[1];
assign d_starved = ~|fd_cir_vld || fd_cir_vld[0] && fd_cir_is_32bit;
wire d_stall = x_stall || d_starved || fd_cir_uop_nonfinal || d_lspair_nonfinal;
assign df_cir_use =
d_starved || d_stall ? 2'h0 :
fd_cir_is_32bit ? 2'h2 : 2'h1;
// CIR Locking is required if we successfully assert a jump request, but
// decode is stalled. It is not possible to gate the jump request if the
// stall depends on bus stall (as this would create a through-path from bus
// stall to bus request) so instead we instruct the frontend to preserve the
// stalled instruction when flushing, and fill in behind it.
//
// Once the stall clears, the stalled instruction can execute its remaining
// side effects e.g. writing a link value to the register file.
wire jump_caused_by_d = f_jump_now && x_jump_not_except;
wire assert_cir_lock = jump_caused_by_d && d_stall;
// CIR lock ends naturally when an instruction (not just uop) graduates to the
// next stage:
wire finished_cir_lock = !d_stall;
// CIR lock can meet an untimely end due to trap entry. One way to reach this
// is a dphase load fault on the final load in a cm.popret: here the `ret`
// issues a fetch address while stalled on the first dphase cycle, then is
// flushed by trap on second cycle.
wire deassert_cir_lock = finished_cir_lock || (f_jump_now && !x_jump_not_except);
reg cir_lock_prev;
wire cir_lock = (cir_lock_prev && !deassert_cir_lock) || assert_cir_lock;
assign df_cir_flush_behind = assert_cir_lock && !cir_lock_prev;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
cir_lock_prev <= 1'b0;
end else begin
cir_lock_prev <= cir_lock;
end
end
reg [W_ADDR-1:0] pc;
wire [W_ADDR-1:0] pc_seq_next = pc + (
|EXTENSION_ZCMP && fd_cir_is_uop && fd_cir_uop_no_pc_update ? 32'd0 :
fd_cir_is_32bit ? 32'd4 : 32'd2
);
assign d_pc = pc;
assign debug_dpc_rdata = pc;
// Frontend should mark the whole instruction, and nothing but the
// instruction, as a predicted branch. This goes wrong when we execute the
// address containing the predicted branch twice with different 16-bit
// alignments (!). We need to issue a branch-to-self to get back on a linear
// path, otherwise PC and CIR will diverge and we will misexecute.
wire partial_predicted_branch = !d_starved &&
|BRANCH_PREDICTOR && fd_cir_is_32bit && ^fd_cir_predbranch;
wire predicted_branch = |BRANCH_PREDICTOR && fd_cir_predbranch[0];
// Generally locking takes place on a stalled jump/branch, which may need the
// original PC available to produce a link address when it unstalls. An
// exception to this is jumps in micro-op sequences: in this case the jump is
// the penultimate instruction in the sequence (ret before addi sp) and we
// need to capture the pc mid-uop-sequence.
wire hold_pc_on_cir_lock = assert_cir_lock && !(fd_cir_is_uop && !fd_cir_uop_no_pc_update && !x_stall);
wire update_pc_on_cir_unlock = cir_lock_prev && finished_cir_lock && !fd_cir_uop_no_pc_update;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
pc <= RESET_VECTOR;
end else begin
if (debug_dpc_wen) begin
pc <= debug_dpc_wdata;
end else if (debug_mode) begin
pc <= pc;
end else if ((f_jump_now && !hold_pc_on_cir_lock) || update_pc_on_cir_unlock) begin
pc <= f_jump_target;
end else if (!f_jump_now && fd_cir_uop_nonfinal && !fd_cir_uop_no_pc_update && !x_stall) begin
// End of previously stalled jr uop in cm.popret and cm.popretz:
// safe to update PC as next instruction (addi sp) cannot trap.
pc <= f_jump_target;
end else if (!d_stall && !cir_lock) begin
// If this instruction is a predicted-taken branch (and has not
// generated a mispredict recovery jump) then set PC to the
// prediction target instead of the sequentially next PC
pc <= predicted_branch ? d_btb_target_addr : pc_seq_next;
end
end
end
`ifdef HAZARD3_ASSERTIONS
always @ (posedge clk) if (rst_n) begin
if (~|fd_cir_vld) assert(!fd_cir_is_uop);
if (fd_cir_uop_no_pc_update) assert(fd_cir_is_uop);
if (fd_cir_uop_nonfinal) assert(fd_cir_is_uop);
if ($past(df_uop_clear)) assert(!fd_cir_is_uop);
// Important to avoid spurious PC updates following a trap on the final
// load of a cm.popret:
if ($past(df_uop_clear)) assert(!fd_cir_uop_no_pc_update);
end
`endif
wire [W_ADDR-1:0] branch_offs =
!fd_cir_is_32bit && predicted_branch ? 32'd2 :
fd_cir_is_32bit && predicted_branch ? 32'd4 : d_imm_b;
always @ (*) begin
casez ({|EXTENSION_A, d_instr[6:2]})
{1'bz, 5'b11011}: d_addr_offs = d_imm_j ; // JAL
{1'bz, 5'b11000}: d_addr_offs = branch_offs ; // Branches
{1'bz, 5'b01000}: d_addr_offs = d_imm_s ; // Store
{1'bz, 5'b11001}: d_addr_offs = d_imm_i ; // JALR
{1'bz, 5'b00000}: d_addr_offs = d_imm_i ; // Loads
{1'b1, 5'b01011}: d_addr_offs = 32'h0000_0000; // Atomics
default: d_addr_offs = 32'hxxxx_xxxx;
endcase
if (partial_predicted_branch) begin
d_addr_offs = 32'h0000_0000;
end
end
// ----------------------------------------------------------------------------
// Track phase of load/store pair instructions (Zilsd and Zclsd)
// This could be shared with uop_ctr (for Zcmp) but the two are fundamentally
// different: Zcmp has 16-bit instructions which expand to sequences of
// 32-bit, whereas Zilsd has multi-phase 32-bit instructions and Zclsd has
// direct 16-bit aliases of those instructions. Therefore it's cleaner to
// separate the phasing from the decompression for Zilsd/Zclsd.
wire d_lspair_phase;
// Reorder accesses to avoid clobbering rs1 (base) in first half of load:
wire d_lspair_reg_sel = d_lspair_phase == d_instr[15];
generate
if (EXTENSION_ZILSD) begin: have_lspair_reg_sel
reg d_lspair_phase_r;
assign d_lspair_phase = d_lspair_phase_r;
reg instr_is_lspair;
always @ (*) begin
casez ({d_invalid || d_starved, d_instr})
{1'b0, `RVOPC_LD}: instr_is_lspair = 1'b1;
{1'b0, `RVOPC_SD}: instr_is_lspair = 1'b1;
default: instr_is_lspair = 1'b0;
endcase
end
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
d_lspair_phase_r <= 1'b0;
end else begin
d_lspair_phase_r <= df_lspair_phase_next;
end
end
assign df_lspair_phase_next =
!d_stall || f_jump_now ? 1'b0 :
instr_is_lspair && !x_stall ? 1'b1 : d_lspair_phase_r;
assign d_lspair_nonfinal = instr_is_lspair && !d_lspair_phase_r;
assign d_lspair_offset = {
29'h0,
d_lspair_reg_sel && instr_is_lspair,
2'h0
};
end else begin: no_lspair_reg_sel
assign d_lspair_phase = 1'b0;
assign df_lspair_phase_next = 1'b0;
assign d_lspair_nonfinal = 1'b0;
assign d_lspair_offset = 32'd0;
end
endgenerate
// ----------------------------------------------------------------------------
// Decode X controls
localparam X0 = {W_REGADDR{1'b0}};
// First decode the instruction bits, based on available extensions and
// privilege state, without gating in any stall/exception signals.
reg [W_REGADDR-1:0] raw_rs1;
reg [W_REGADDR-1:0] raw_rs2;
reg [W_REGADDR-1:0] raw_rd;
reg [W_DATA-1:0] raw_imm;
reg [W_ALUSRC-1:0] raw_alusrc_a;
reg [W_ALUSRC-1:0] raw_alusrc_b;
reg [W_ALUOP-1:0] raw_aluop;
reg [W_MEMOP-1:0] raw_memop;
reg [W_MULOP-1:0] raw_mulop;
reg raw_csr_ren;
reg raw_csr_wen;
reg [1:0] raw_csr_wtype;
reg raw_csr_w_imm;
reg [W_BCOND-1:0] raw_branchcond;
reg raw_addr_is_regoffs;
reg [W_EXCEPT-1:0] raw_except;
reg raw_sleep_wfi;
reg raw_sleep_block;
reg raw_sleep_unblock;
reg raw_fence_i;
reg raw_fence_d;
always @ (*) begin
// Assign some defaults
raw_rs1 = d_instr[19:15];
raw_rs2 = d_instr[24:20];
raw_rd = d_instr[11: 7];
raw_imm = d_imm_i;
raw_alusrc_a = ALUSRCA_RS1;
raw_alusrc_b = ALUSRCB_RS2;
raw_aluop = ALUOP_ADD;
raw_memop = MEMOP_NONE;
raw_mulop = M_OP_MUL;
raw_csr_ren = 1'b0;
raw_csr_wen = 1'b0;
raw_csr_wtype = CSR_WTYPE_W;
raw_csr_w_imm = 1'b0;
raw_branchcond = BCOND_NEVER;
raw_addr_is_regoffs = 1'b0;
raw_except = EXCEPT_NONE;
raw_sleep_wfi = 1'b0;
raw_sleep_block = 1'b0;
raw_sleep_unblock = 1'b0;
raw_fence_i = 1'b0;
raw_fence_d = 1'b0;
// Note this funct3/funct7 are valid only for 32-bit instructions. They
// are useful for clusters of related ALU ops, such as sh*add, clmul.
d_funct3_32b = fd_cir[14:12];
d_funct7_32b = fd_cir[31:25];
d_invalid_32bit = 1'b0;
casez (d_instr)
`RVOPC_BEQ: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_SUB; raw_branchcond = BCOND_ZERO; end
`RVOPC_BNE: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_SUB; raw_branchcond = BCOND_NZERO; end
`RVOPC_BLT: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_LT; raw_branchcond = BCOND_NZERO; end
`RVOPC_BGE: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_LT; raw_branchcond = BCOND_ZERO; end
`RVOPC_BLTU: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_LTU; raw_branchcond = BCOND_NZERO; end
`RVOPC_BGEU: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_rd = X0; raw_aluop = ALUOP_LTU; raw_branchcond = BCOND_ZERO; end
`RVOPC_JALR: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_branchcond = BCOND_ALWAYS; raw_addr_is_regoffs = 1'b1;
raw_rs2 = X0; raw_aluop = ALUOP_ADD; raw_alusrc_a = ALUSRCA_PC; raw_alusrc_b = ALUSRCB_IMM; raw_imm = fd_cir_is_32bit ? 32'd4 : 32'd2; end
`RVOPC_JAL: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_branchcond = BCOND_ALWAYS; raw_rs1 = X0;
raw_rs2 = X0; raw_aluop = ALUOP_ADD; raw_alusrc_a = ALUSRCA_PC; raw_alusrc_b = ALUSRCB_IMM; raw_imm = fd_cir_is_32bit ? 32'd4 : 32'd2; end
`RVOPC_LUI: begin raw_aluop = ALUOP_RS2; raw_imm = d_imm_u; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; raw_rs1 = X0; end
`RVOPC_AUIPC: begin d_invalid_32bit = DEBUG_SUPPORT && debug_mode; raw_aluop = ALUOP_ADD; raw_imm = d_imm_u; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; raw_alusrc_a = ALUSRCA_PC; raw_rs1 = X0; end
`RVOPC_ADDI: begin raw_aluop = ALUOP_ADD; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end
`RVOPC_SLLI: begin raw_aluop = ALUOP_SLL; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end
`RVOPC_SLTI: begin raw_aluop = ALUOP_LT; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end
`RVOPC_SLTIU: begin raw_aluop = ALUOP_LTU; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end
`RVOPC_XORI: begin raw_aluop = ALUOP_XOR; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end
`RVOPC_SRLI: begin raw_aluop = ALUOP_SRL; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end
`RVOPC_SRAI: begin raw_aluop = ALUOP_SRA; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end
`RVOPC_ORI: begin raw_aluop = ALUOP_OR; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end
`RVOPC_ANDI: begin raw_aluop = ALUOP_AND; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; raw_rs2 = X0; end
`RVOPC_ADD: begin raw_aluop = ALUOP_ADD; end
`RVOPC_SUB: begin raw_aluop = ALUOP_SUB; end
`RVOPC_SLL: begin raw_aluop = ALUOP_SLL; end
`RVOPC_SLTU: begin raw_aluop = ALUOP_LTU; end
`RVOPC_XOR: begin raw_aluop = ALUOP_XOR; end
`RVOPC_SRL: begin raw_aluop = ALUOP_SRL; end
`RVOPC_SRA: begin raw_aluop = ALUOP_SRA; end
`RVOPC_OR: begin raw_aluop = ALUOP_OR; end
`RVOPC_AND: begin raw_aluop = ALUOP_AND; end
`RVOPC_LB: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LB; end
`RVOPC_LH: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LH; end
`RVOPC_LW: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LW; end
`RVOPC_LBU: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LBU; end
`RVOPC_LHU: begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LHU; end
`RVOPC_SB: begin raw_addr_is_regoffs = 1'b1; raw_aluop = ALUOP_RS2; raw_memop = MEMOP_SB; raw_rd = X0; end
`RVOPC_SH: begin raw_addr_is_regoffs = 1'b1; raw_aluop = ALUOP_RS2; raw_memop = MEMOP_SH; raw_rd = X0; end
`RVOPC_SW: begin raw_addr_is_regoffs = 1'b1; raw_aluop = ALUOP_RS2; raw_memop = MEMOP_SW; raw_rd = X0; end
`RVOPC_SLT: begin
raw_aluop = ALUOP_LT;
if (|EXTENSION_XH3POWER && ~|raw_rd && ~|raw_rs1) begin
if (raw_rs2 == 5'h00) begin
// h3.block (power management hint)
d_invalid_32bit = trap_wfi;
raw_sleep_block = !trap_wfi;
end else if (raw_rs2 == 5'h01) begin
// h3.unblock (power management hint)
d_invalid_32bit = trap_wfi;
raw_sleep_unblock = !trap_wfi;
end
end
end
`RVOPC_MUL: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_MUL; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_MULH: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_MULH; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_MULHSU: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_MULHSU; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_MULHU: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_MULHU; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_DIV: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_DIV; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_DIVU: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_DIVU; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_REM: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_REM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_REMU: if (EXTENSION_M) begin raw_aluop = ALUOP_MULDIV; raw_mulop = M_OP_REMU; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_LR_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_LR_W; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_SC_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_SC_W; raw_aluop = ALUOP_RS2; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_AMOSWAP_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_RS2; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_AMOADD_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_ADD; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_AMOXOR_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_XOR; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_AMOAND_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_AND; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_AMOOR_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_OR; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_AMOMIN_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_MIN; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_AMOMAX_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_MAX; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_AMOMINU_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_MINU; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_AMOMAXU_W: if (EXTENSION_A) begin raw_addr_is_regoffs = 1'b1; raw_memop = MEMOP_AMO; raw_aluop = ALUOP_MAXU; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_LD: if (EXTENSION_ZILSD) begin raw_addr_is_regoffs = 1'b1; raw_rs2 = X0; raw_memop = MEMOP_LW; raw_rd = {d_instr[11: 8], d_lspair_reg_sel}; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_SD: if (EXTENSION_ZILSD) begin raw_addr_is_regoffs = 1'b1; raw_rd = X0; raw_memop = MEMOP_SW; raw_rs2 = {d_instr[24:21], d_lspair_reg_sel}; raw_aluop = ALUOP_RS2; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_SH1ADD: if (EXTENSION_ZBA) begin raw_aluop = ALUOP_SHXADD; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_SH2ADD: if (EXTENSION_ZBA) begin raw_aluop = ALUOP_SHXADD; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_SH3ADD: if (EXTENSION_ZBA) begin raw_aluop = ALUOP_SHXADD; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_ANDN: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ANDN; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CLZ: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_CLZ; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CPOP: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_CPOP; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CTZ: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_CTZ; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_MAX: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_MAX; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_MAXU: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_MAXU; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_MIN: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_MIN; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_MINU: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_MINU; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_ORC_B: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ORC_B; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_ORN: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ORN; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_REV8: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_REV8; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_ROL: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ROL; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_ROR: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ROR; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_RORI: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ROR; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_SEXT_B: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_SEXT_B; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_SEXT_H: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_SEXT_H; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_XNOR: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_XNOR; end else begin d_invalid_32bit = 1'b1; end
// Note: ZEXT_H is a subset of PACK from Zbkb. This is fine as long
// as this case appears first, since Zbkb implies Zbb on Hazard3.
`RVOPC_ZEXT_H: if (EXTENSION_ZBB) begin raw_aluop = ALUOP_ZEXT_H; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CLMUL: if (EXTENSION_ZBC) begin raw_aluop = ALUOP_CLMUL; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CLMULH: if (EXTENSION_ZBC) begin raw_aluop = ALUOP_CLMUL; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CLMULR: if (EXTENSION_ZBC) begin raw_aluop = ALUOP_CLMUL; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_BCLR: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BCLR; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_BCLRI: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BCLR; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_BEXT: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BEXT; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_BEXTI: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BEXT; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_BINV: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BINV; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_BINVI: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BINV; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_BSET: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BSET; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_BSETI: if (EXTENSION_ZBS) begin raw_aluop = ALUOP_BSET; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_PACK: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_PACK; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_PACKH: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_PACKH; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_BREV8: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_BREV8; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_UNZIP: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_UNZIP; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_ZIP: if (EXTENSION_ZBKB) begin raw_aluop = ALUOP_ZIP; raw_rs2 = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_XPERM8: if (EXTENSION_ZBKX) begin raw_aluop = ALUOP_XPERM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_XPERM4: if (EXTENSION_ZBKX) begin raw_aluop = ALUOP_XPERM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_H3_BEXTM: if (EXTENSION_XH3BEXTM) begin
raw_aluop = ALUOP_BEXTM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_H3_BEXTMI: if (EXTENSION_XH3BEXTM) begin
raw_aluop = ALUOP_BEXTM; raw_rs2 = X0; raw_imm = d_imm_i; raw_alusrc_b = ALUSRCB_IMM; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CSRRW: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = 1'b1 ; raw_csr_ren = |raw_rd; raw_csr_wtype = CSR_WTYPE_W; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CSRRS: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = |raw_rs1; raw_csr_ren = 1'b1 ; raw_csr_wtype = CSR_WTYPE_S; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CSRRC: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = |raw_rs1; raw_csr_ren = 1'b1 ; raw_csr_wtype = CSR_WTYPE_C; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CSRRWI: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = 1'b1 ; raw_csr_ren = |raw_rd; raw_csr_wtype = CSR_WTYPE_W; raw_csr_w_imm = 1'b1; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CSRRSI: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = |raw_rs1; raw_csr_ren = 1'b1 ; raw_csr_wtype = CSR_WTYPE_S; raw_csr_w_imm = 1'b1; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_CSRRCI: if (HAVE_CSR) begin raw_rs2 = X0; raw_imm = d_imm_i; raw_csr_wen = |raw_rs1; raw_csr_ren = 1'b1 ; raw_csr_wtype = CSR_WTYPE_C; raw_csr_w_imm = 1'b1; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_FENCE: begin raw_rs2 = X0; raw_fence_d = 1'b1; end // Note rs1/rd are zero in instruction
`RVOPC_FENCE_I: if (EXTENSION_ZIFENCEI) begin raw_except = debug_mode ? EXCEPT_NONE : EXCEPT_REFETCH; raw_fence_i = 1'b1; end else begin d_invalid_32bit = 1'b1; end // note rs1/rs2/rd are zero in instruction
`RVOPC_ECALL: if (HAVE_CSR) begin raw_except = m_mode || !U_MODE ? EXCEPT_ECALL_M : EXCEPT_ECALL_U; raw_rs2 = X0; raw_rs1 = X0; raw_rd = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_EBREAK: if (HAVE_CSR) begin raw_except = EXCEPT_EBREAK; raw_rs2 = X0; raw_rs1 = X0; raw_rd = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_MRET: if (HAVE_CSR && m_mode) begin raw_except = EXCEPT_MRET; raw_rs2 = X0; raw_rs1 = X0; raw_rd = X0; end else begin d_invalid_32bit = 1'b1; end
`RVOPC_WFI: if (HAVE_CSR && !trap_wfi) begin raw_sleep_wfi = 1'b1; raw_rs2 = X0; raw_rs1 = X0; raw_rd = X0; end else begin d_invalid_32bit = 1'b1; end
default: begin d_invalid_32bit = 1'b1; end
endcase
if (|EXTENSION_E && (raw_rd[4] || raw_rs1[4] || raw_rs2[4])) begin
d_invalid_32bit = 1'b1;
end
end
// Then gate key signals based on CIR fullness, fetch faults etc. The split
// helps to avoid an event scheduling feedback loop that makes simulators
// unhappy and slow, particularly verilator
localparam [4:0] REGADDR_MASK = {~|EXTENSION_E, 4'hf};
always @ (*) begin
// Pass through by default
d_rs1 = raw_rs1 & REGADDR_MASK;
d_rs2 = raw_rs2 & REGADDR_MASK;
d_rd = raw_rd & REGADDR_MASK;
d_imm = raw_imm;
d_alusrc_a = raw_alusrc_a;
d_alusrc_b = raw_alusrc_b;
d_aluop = raw_aluop;
d_memop = raw_memop;
d_mulop = raw_mulop;
d_csr_ren = raw_csr_ren;
d_csr_wen = raw_csr_wen;
d_csr_wtype = raw_csr_wtype;
d_csr_w_imm = raw_csr_w_imm;
d_branchcond = raw_branchcond;
d_addr_is_regoffs = raw_addr_is_regoffs;
d_except = raw_except;
d_sleep_wfi = raw_sleep_wfi;
d_sleep_block = raw_sleep_block;
d_sleep_unblock = raw_sleep_unblock;
d_fence_i = raw_fence_i;
d_fence_d = raw_fence_d;
if (d_invalid || d_starved || d_except_instr_bus_fault || partial_predicted_branch) begin
d_rs1 = {W_REGADDR{1'b0}};
d_rs2 = {W_REGADDR{1'b0}};
d_rd = {W_REGADDR{1'b0}};
d_memop = MEMOP_NONE;
d_branchcond = BCOND_NEVER;
d_csr_ren = 1'b0;
d_csr_wen = 1'b0;
d_except = EXCEPT_NONE;
d_sleep_wfi = 1'b0;
d_sleep_block = 1'b0;
d_sleep_unblock = 1'b0;
d_fence_i = 1'b0;
d_fence_d = 1'b0;
if (EXTENSION_M)
d_aluop = ALUOP_ADD;
if (d_except_instr_bus_fault)
d_except = EXCEPT_INSTR_FAULT;
else if (d_invalid && !d_starved)
d_except = EXCEPT_INSTR_ILLEGAL;
end
if (partial_predicted_branch) begin
d_addr_is_regoffs = 1'b0;
d_branchcond = BCOND_ALWAYS;
end
if (cir_lock_prev) begin
d_branchcond = BCOND_NEVER;
end
end
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+906
View File
@@ -0,0 +1,906 @@
/*****************************************************************************\
| Copyright (C) 2021-2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
`default_nettype none
module hazard3_frontend #(
`include "hazard3_config.vh"
) (
input wire clk,
input wire rst_n,
// Fetch interface
// addr_vld may be asserted at any time, but after assertion,
// neither addr nor addr_vld may change until the cycle after addr_rdy.
// There is no backpressure on the data interface; the front end
// must ensure it does not request data it cannot receive.
// addr_rdy and dat_vld may be functions of hready, and
// may not be used to compute combinational outputs.
output wire mem_size, // 1'b1 -> 32 bit access
output wire [W_ADDR-1:0] mem_addr,
output wire mem_priv,
output wire mem_addr_vld,
input wire mem_addr_rdy,
input wire [W_DATA-1:0] mem_data,
input wire mem_data_err,
input wire mem_data_vld,
// Jump/flush interface
// Processor may assert vld at any time. The request will not go through
// unless rdy is high. Processor *may* alter request during this time.
// Inputs must not be a function of hready.
input wire [W_ADDR-1:0] jump_target,
input wire jump_priv,
input wire jump_target_vld,
output wire jump_target_rdy,
// Interface to the branch target buffer. `src_addr` is the address of the
// last halfword of a taken backward branch. The frontend redirects fetch
// such that `src_addr` appears to be sequentially followed by `target`.
input wire btb_set,
input wire [W_ADDR-1:0] btb_set_src_addr,
input wire btb_set_src_size,
input wire [W_ADDR-1:0] btb_set_target_addr,
input wire btb_clear,
output wire [W_ADDR-1:0] btb_target_addr_out,
// Interface to Decode
output reg [31:0] cir, // Current instruction register; pre-expanded to 32-bit
output wire [31:0] cir_raw, // Unexpanded instruction data
output reg [1:0] cir_vld, // number of valid halfwords in CIR
input wire [1:0] cir_use, // number of halfwords D intends to consume
// *may* be a function of hready
output wire [1:0] cir_err, // Bus error on upper/lower halfword of CIR.
output wire [1:0] cir_predbranch, // Set for last halfword of a predicted-taken branch
output wire cir_break_any, // Set for exact match of a breakpoint address on CIR LSB
output wire cir_break_d_mode, // As above but specifically break to debug mode
output reg cir_is_32bit, // Can't be decoded from CIR due to pre-expansion
output reg cir_invalid_16bit, // Expanded an invalid 32-bit instruction
output reg cir_is_uop, // Current instruction is part of a micro-op sequence
output reg cir_uop_nonfinal, // ...and there are more to follow in this instruction
output reg cir_uop_no_pc_update, // Suppress PC increment or jump (note the jump in cm.popret is not the final uop!)
output reg cir_uop_atomic, // Prevent IRQ entry, so intermediate states are not observed
input wire uop_stall,
input wire uop_clear,
// "flush_behind": do not flush the oldest instruction when accepting a
// jump request (but still flush younger instructions). Sometimes a
// stalled instruction may assert a jump request, because e.g. the stall
// is dependent on a bus stall signal so can't gate the request.
input wire cir_flush_behind,
// Required for regnum predecode when Zilsd is enabled:
input wire df_lspair_phase_next,
// Signal to power controller that power down is safe. (When going to
// sleep, first the pipeline is stalled, and then the power controller
// waits for the frontend to naturally come to a halt before releasing
// its power request. This avoids manually halting the frontend.)
output wire pwrdown_ok,
// Signal to delay the first instruction fetch following reset, because
// powerup has not yet been negotiated.
input wire delay_first_fetch,
// Provide the rs1/rs2 register numbers which will be in CIR next cycle.
// Coarse: valid if this instruction has a nonzero register operand.
// (Suitable for regfile read)
output reg [4:0] predecode_rs1_coarse,
output reg [4:0] predecode_rs2_coarse,
// Fine: like coarse, but accurate zeroing when the operand is implicit.
// (Suitable for bypass. Still not precise enough for stall logic.)
output reg [4:0] predecode_rs1_fine,
output reg [4:0] predecode_rs2_fine,
// Debugger instruction injection: instruction fetch is suppressed when in
// debug halt state, and the DM can then inject instructions into the last
// entry of the prefetch queue using the vld/rdy handshake.
input wire debug_mode,
input wire [W_DATA-1:0] dbg_instr_data,
input wire dbg_instr_data_vld,
output wire dbg_instr_data_rdy,
// PMP query->kill interface for X permission checks
output wire [W_ADDR-1:0] pmp_i_addr,
output wire pmp_i_m_mode,
input wire pmp_i_kill,
// Trigger unit query->break interface for breakpoints
output wire [W_ADDR-1:0] trigger_addr,
output wire trigger_m_mode,
input wire [1:0] trigger_break_any,
input wire [1:0] trigger_break_d_mode
);
`include "rv_opcodes.vh"
localparam W_BUNDLE = 16;
// This is the minimum for full throughput (enough to avoid dropping data when
// decode stalls) and there is no significant advantage to going larger.
localparam FIFO_DEPTH = 2;
// ----------------------------------------------------------------------------
// Fetch queue
wire jump_now = jump_target_vld && jump_target_rdy;
reg [1:0] mem_data_hwvld;
// PMP X faults are checked in parallel with the fetch (fine if executable
// memory is read-idempotent) and failures are promoted to bus errors:
wire pmp_kill_fetch_dph;
wire mem_or_pmp_err = mem_data_err || pmp_kill_fetch_dph;
// Similarly, breakpoint matches are checked during fetch data phase. These
// are called mem_xxx because they are the breakpoint metadata for the data
// coming back from memory in this dphase.
wire [1:0] mem_break_any;
wire [1:0] mem_break_d_mode;
// Mark data as containing a predicted-taken branch instruction so that
// mispredicts can be recovered -- need to track both halfwords so that we
// can mark the entire instruction, and nothing but the instruction:
reg [1:0] mem_data_predbranch;
// Bus errors (and other metadata) travel alongside data. They cause an
// exception if the core decodes the instruction, but until then can be
// flushed harmlessly.
reg [W_DATA-1:0] fifo_mem [0:FIFO_DEPTH];
reg fifo_err [0:FIFO_DEPTH];
reg [1:0] fifo_break_any [0:FIFO_DEPTH];
reg [1:0] fifo_break_d_mode [0:FIFO_DEPTH];
reg [1:0] fifo_predbranch [0:FIFO_DEPTH];
reg [1:0] fifo_valid_hw [0:FIFO_DEPTH];
reg fifo_valid [0:FIFO_DEPTH];
reg fifo_valid_m1 [0:FIFO_DEPTH];
wire [W_DATA-1:0] fifo_rdata = fifo_mem[0];
wire fifo_full = fifo_valid[FIFO_DEPTH - 1];
wire fifo_empty = !fifo_valid[0];
wire fifo_almost_full = fifo_valid[FIFO_DEPTH - 2];
wire fifo_push;
wire fifo_pop;
wire fifo_dbg_inject = DEBUG_SUPPORT && dbg_instr_data_vld && dbg_instr_data_rdy;
always @ (*) begin: boundary_conditions
integer i;
fifo_mem[FIFO_DEPTH] = mem_data;
fifo_predbranch[FIFO_DEPTH] = 2'b00;
fifo_err[FIFO_DEPTH] = 1'b0;
fifo_break_any[FIFO_DEPTH] = 2'b00;
fifo_break_d_mode[FIFO_DEPTH] = 2'b00;
for (i = 0; i <= FIFO_DEPTH; i = i + 1) begin
fifo_valid[i] = |EXTENSION_C ? |fifo_valid_hw[i] : fifo_valid_hw[i][0];
// valid-to-right condition: i == 0 || fifo_valid[i - 1], but without
// using negative array bound (seems broken in Yosys?) or OOB in the
// short circuit case (gives lint although result is well-defined)
if (i == 0) begin
fifo_valid_m1[i] = 1'b1;
end else begin
fifo_valid_m1[i] = fifo_valid[i - 1];
end
end
end
always @ (posedge clk or negedge rst_n) begin: fifo_update
integer i;
if (!rst_n) begin
for (i = 0; i < FIFO_DEPTH; i = i + 1) begin
fifo_valid_hw[i] <= 2'b00;
fifo_mem[i] <= 32'd0;
fifo_err[i] <= 1'b0;
fifo_break_any[i] <= 2'b00;
fifo_break_d_mode[i] <= 2'b00;
fifo_predbranch[i] <= 2'b00;
end
// This exists only for loop boundary conditions, but is tied off in
// this synchronous process to work around a Verilator scheduling
// issue (see issue #21)
fifo_valid_hw[FIFO_DEPTH] <= 2'b00;
end else begin
for (i = 0; i < FIFO_DEPTH; i = i + 1) begin
if (fifo_pop || (fifo_push && !fifo_valid[i])) begin
fifo_mem[i] <= fifo_valid[i + 1] ? fifo_mem[i + 1] : mem_data;
fifo_err[i] <= fifo_valid[i + 1] ? fifo_err[i + 1] : mem_or_pmp_err;
fifo_break_any[i] <= fifo_valid[i + 1] ? fifo_break_any[i + 1] : mem_break_any;
fifo_break_d_mode[i] <= fifo_valid[i + 1] ? fifo_break_d_mode[i + 1] : mem_break_d_mode;
fifo_predbranch[i] <= fifo_valid[i + 1] ? fifo_predbranch[i + 1] : mem_data_predbranch;
end
fifo_valid_hw[i] <=
jump_now ? 2'h0 :
fifo_valid[i + 1] && fifo_pop ? fifo_valid_hw[i + 1] :
fifo_valid[i] && fifo_pop ? mem_data_hwvld & {2{fifo_push}} :
fifo_valid[i] ? fifo_valid_hw[i] :
fifo_push && !fifo_pop && fifo_valid_m1[i] ? mem_data_hwvld : 2'h0;
end
// Allow DM to inject instructions directly into the lowest-numbered
// queue entry. This mux should not extend critical path since it is
// balanced with the instruction-assembly muxes on the queue bypass
// path. Note that flush takes precedence over debug injection
// (and the debug module design must account for this)
if (fifo_dbg_inject) begin
fifo_mem[0] <= dbg_instr_data;
fifo_err[0] <= 1'b0;
fifo_predbranch[0] <= 2'b00;
fifo_break_any[0] <= 2'b00;
fifo_break_d_mode[0] <= 2'b00;
fifo_valid_hw[0] <= jump_now ? 2'b00 : 2'b11;
end
fifo_valid_hw[FIFO_DEPTH] <= 2'b00;
end
end
`ifdef HAZARD3_ASSERTIONS
always @ (posedge clk) if (rst_n) begin
// FIFO validity must be compact, so we can always consume from the end
if (!fifo_valid[0]) begin
assert(!fifo_valid[1]);
end
end
`endif
assign pwrdown_ok = (fifo_full && !jump_target_vld) || debug_mode;
// ----------------------------------------------------------------------------
// Branch target buffer
wire [W_ADDR-1:0] btb_src_addr;
wire btb_src_size;
wire [W_ADDR-1:0] btb_target_addr;
wire btb_valid;
generate
if (BRANCH_PREDICTOR) begin: have_btb
reg [W_ADDR-1:0] btb_src_addr_r;
reg btb_src_size_r;
reg [W_ADDR-1:0] btb_target_addr_r;
reg btb_valid_r;
assign btb_src_addr = btb_src_addr_r;
assign btb_src_size = btb_src_size_r;
assign btb_target_addr = btb_target_addr_r;
assign btb_valid = btb_valid_r;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
btb_src_addr_r <= {W_ADDR{1'b0}};
btb_src_size_r <= 1'b0;
btb_target_addr_r <= {W_ADDR{1'b0}};
btb_valid_r <= 1'b0;
end else if (btb_clear) begin
// Clear takes precedences over set. E.g. if a taken branch is in
// stage 2 and an exception is in stage 3, we must clear the BTB.
btb_valid_r <= 1'b0;
end else if (btb_set) begin
btb_src_addr_r <= btb_set_src_addr;
btb_src_size_r <= btb_set_src_size;
btb_target_addr_r <= btb_set_target_addr;
btb_valid_r <= 1'b1;
end
end
end else begin: no_btb
assign btb_src_addr = {W_ADDR{1'b0}};
assign btb_src_size = 1'b0;
assign btb_target_addr = {W_ADDR{1'b0}};
assign btb_valid = 1'b0;
end
endgenerate
// Decode uses the target address to set the PC to the correct branch target
// value following a predicted-taken branch (as normally it would update PC
// by following an X jump request, and in this case there is none).
//
// Note this assumes the BTB target has not changed by the time the predicted
// branch arrives at decode! This is always true because the only way for the
// target address to change is when an older branch is taken, which would
// flush the younger predicted-taken branch before it reaches decode.
assign btb_target_addr_out = btb_target_addr;
// ----------------------------------------------------------------------------
// Fetch request generation
// Fetch addr runs ahead of the PC, in word increments.
reg [W_ADDR-1:0] fetch_addr;
reg fetch_priv;
reg btb_prev_start_of_overhanging;
reg [1:0] mem_aph_hwvld;
reg mem_addr_hold;
wire btb_match_word = |BRANCH_PREDICTOR && btb_valid && (
fetch_addr[W_ADDR-1:2] == btb_src_addr[W_ADDR-1:2]
);
// Catch case where predicted-taken branch instruction extends into next word:
wire btb_src_overhanging = btb_src_size && btb_src_addr[1];
// Suppress case where we have jumped immediately after a word-aligned halfword-sized
// branch, and the jump target went into fetch_addr due to an address-phase hold:
wire btb_jumped_beyond = !btb_src_size && !btb_src_addr[1] && !mem_aph_hwvld[0];
wire btb_match_current_addr = btb_match_word && !btb_src_overhanging && !btb_jumped_beyond;
wire btb_match_next_addr = btb_match_word && btb_src_overhanging;
wire btb_match_now = btb_match_current_addr || btb_prev_start_of_overhanging;
// Post-increment if jump request is going straight through
wire [W_ADDR-1:0] jump_target_post_increment =
{jump_target[W_ADDR-1:2], 2'b00} +
{{W_ADDR-3{1'b0}}, mem_addr_rdy && !mem_addr_hold, 2'b00};
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
fetch_addr <= RESET_VECTOR;
// M-mode at reset:
fetch_priv <= 1'b1;
btb_prev_start_of_overhanging <= 1'b0;
end else begin
if (jump_now) begin
fetch_addr <= jump_target_post_increment;
fetch_priv <= jump_priv || !U_MODE;
btb_prev_start_of_overhanging <= 1'b0;
end else if (mem_addr_vld && mem_addr_rdy) begin
if (btb_match_now && |BRANCH_PREDICTOR) begin
fetch_addr <= {btb_target_addr[W_ADDR-1:2], 2'b00};
end else begin
fetch_addr <= fetch_addr + 32'd4;
end
btb_prev_start_of_overhanging <= btb_match_next_addr;
end
end
end
// Combinatorially generate the address-phase request
reg reset_holdoff;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
reset_holdoff <= 1'b1;
end else begin
reset_holdoff <= (|EXTENSION_XH3POWER && delay_first_fetch) ? reset_holdoff : 1'b0;
// This should be impossible, but assert to be sure, because it *will*
// change the fetch address (and we shouldn't check it in hardware if
// we can prove it doesn't happen)
end
end
`ifdef HAZARD3_ASSERTIONS
always @ (posedge clk) if (rst_n) begin
assert(!(jump_target_vld && reset_holdoff));
end
`endif
reg [W_ADDR-1:0] mem_addr_r;
reg mem_priv_r;
reg mem_addr_vld_r;
// Downstream accesses are always word-sized word-aligned.
assign mem_addr = mem_addr_r;
assign mem_priv = mem_priv_r;
assign mem_addr_vld = mem_addr_vld_r && !reset_holdoff;
assign mem_size = 1'b1;
wire fetch_stall;
always @ (*) begin
mem_addr_r = fetch_addr;
mem_priv_r = fetch_priv;
mem_addr_vld_r = 1'b1;
case (1'b1)
mem_addr_hold : begin mem_addr_r = fetch_addr; end
jump_target_vld || reset_holdoff : begin
mem_addr_r = {jump_target[W_ADDR-1:2], 2'b00};
mem_priv_r = jump_priv || !U_MODE;
end
DEBUG_SUPPORT && debug_mode : begin mem_addr_vld_r = 1'b0; end
!fetch_stall : begin mem_addr_r = fetch_addr; end
default : begin mem_addr_vld_r = 1'b0; end
endcase
end
assign jump_target_rdy = !mem_addr_hold;
// ----------------------------------------------------------------------------
// Bus Pipeline Tracking
// Keep track of some useful state of the memory interface
reg [1:0] pending_fetches;
reg [1:0] ctr_flush_pending;
wire [1:0] pending_fetches_next = pending_fetches + (mem_addr_vld && !mem_addr_hold) - mem_data_vld;
// Using the non-registered version of pending_fetches would improve FIFO
// utilisation, but create a combinatorial path from hready to address phase!
// This means at least a 2-word FIFO is required for full fetch throughput.
assign fetch_stall = fifo_full
|| fifo_almost_full && |pending_fetches
|| pending_fetches > 2'h1;
// Debugger only injects instructions when the frontend is at rest and empty.
assign dbg_instr_data_rdy = DEBUG_SUPPORT && !fifo_valid[0] && ~|ctr_flush_pending;
wire cir_room_for_fetch;
// If fetch data is forwarded past the FIFO, ensure it is not also written to it.
assign fifo_push = mem_data_vld && ~|ctr_flush_pending && !(cir_room_for_fetch && fifo_empty)
&& !(DEBUG_SUPPORT && debug_mode);
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
mem_addr_hold <= 1'b0;
pending_fetches <= 2'h0;
ctr_flush_pending <= 2'h0;
end else begin
mem_addr_hold <= mem_addr_vld && !mem_addr_rdy;
pending_fetches <= pending_fetches_next;
if (jump_now) begin
ctr_flush_pending <= pending_fetches - mem_data_vld;
end else if (|ctr_flush_pending && mem_data_vld) begin
ctr_flush_pending <= ctr_flush_pending - 1'b1;
end
end
end
`ifdef HAZARD3_ASSERTIONS
always @ (posedge clk) if (rst_n) begin
assert(ctr_flush_pending <= pending_fetches);
assert(pending_fetches < 2'd3);
assert(!(mem_data_vld && !pending_fetches));
end
`endif
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
mem_data_hwvld <= 2'b11;
mem_aph_hwvld <= 2'b11;
mem_data_predbranch <= 2'b00;
end else begin
if (jump_now) begin
if (|EXTENSION_C) begin
if (mem_addr_rdy) begin
mem_aph_hwvld <= 2'b11;
mem_data_hwvld <= {1'b1, !jump_target[1]};
end else begin
mem_aph_hwvld <= {1'b1, !jump_target[1]};
end
end
mem_data_predbranch <= 2'b00;
end else if (mem_addr_vld && mem_addr_rdy) begin
if (|EXTENSION_C) begin
// If a predicted-taken branch instruction only spans the first
// half of a word, need to flag the second half as invalid.
mem_data_hwvld <= mem_aph_hwvld & {
!(|BRANCH_PREDICTOR && btb_match_now && (btb_src_addr[1] == btb_src_size)),
1'b1
};
// Also need to take the alignment of the destination into account.
mem_aph_hwvld <= {
1'b1,
!(|BRANCH_PREDICTOR && btb_match_now && btb_target_addr[1])
};
end
mem_data_predbranch <=
|BRANCH_PREDICTOR && btb_match_word ? (
btb_src_addr[1] ? 2'b10 :
btb_src_size ? 2'b11 : 2'b01
) :
|BRANCH_PREDICTOR && btb_prev_start_of_overhanging ? (
2'b01
) : 2'b00;
end
end
end
// ----------------------------------------------------------------------------
// PMP and trigger unit interfacing: query -> kill/break
wire [W_ADDR-1:0] pmp_trigger_check_dph_addr;
wire pmp_trigger_check_dph_m_mode;
// Register the fetch address into stage F so that the PMP can check it in
// parallel with the bus data phase. Feels wasteful to have a separate
// register, but using the fetch_addr counter is fraught due to the way that
// new addresses go into it or past it (depending on aphase hold).
generate
if (PMP_REGIONS > 0 || DEBUG_SUPPORT != 0) begin: have_check_reg
reg [W_ADDR-1:0] check_addr_dph;
reg check_m_mode_dph;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
check_addr_dph <= {W_ADDR{1'b0}};
check_m_mode_dph <= 1'b0;
end else if (mem_addr_vld && mem_addr_rdy) begin
check_addr_dph <= mem_addr;
check_m_mode_dph <= mem_priv;
end
end
assign pmp_trigger_check_dph_addr = check_addr_dph;
assign pmp_trigger_check_dph_m_mode = check_m_mode_dph;
end else begin: no_check_reg
assign pmp_trigger_check_dph_addr = {W_ADDR{1'b0}};
assign pmp_trigger_check_dph_m_mode = 1'b0;
end
endgenerate
generate
if (PMP_REGIONS == 0) begin: no_pmp
assign pmp_i_addr = {W_ADDR{1'b0}};
assign pmp_i_m_mode = 1'b0;
assign pmp_kill_fetch_dph = 1'b0;
end else begin: have_pmp
assign pmp_i_addr = pmp_trigger_check_dph_addr;
assign pmp_i_m_mode = pmp_trigger_check_dph_m_mode;
assign pmp_kill_fetch_dph = pmp_i_kill && !debug_mode;
end
endgenerate
generate
if (DEBUG_SUPPORT == 0) begin: no_triggers
assign trigger_addr = {W_ADDR{1'b0}};
assign trigger_m_mode = 1'b0;
assign mem_break_any = 2'b00;
assign mem_break_d_mode = 2'b00;
end else begin: have_triggers
assign trigger_addr = pmp_trigger_check_dph_addr;
assign trigger_m_mode = pmp_trigger_check_dph_m_mode;
assign mem_break_any = trigger_break_any & {|EXTENSION_C, 1'b1};
assign mem_break_d_mode = trigger_break_d_mode & {|EXTENSION_C, 1'b1};
end
endgenerate
// ----------------------------------------------------------------------------
// Instruction buffer
// The instruction buffer is a 3 x ~16-bit shift register:
//
// * 2 x 16-bit entries form the 32-bit current instruction register (CIR)
// which is the processor's decode window
//
// * 1 x 16-bit entry allows the decode window to be non-32-bit-aligned with
// respect to the 2 x 32-bit prefetch queue entries, which are always
// naturally aligned in memory (if fully populated).
//
// The third entry should be trimmed for non-RVC configurations due to
// constant-folding on EXTENSION_C; it is unnecessary here because the
// instructions are always 32-bit-aligned.
// The entries ("slots") are slightly larger than 16 bits because they also
// contain metadata like bus errors:
localparam W_SLOT = 4 + W_BUNDLE;
localparam SLOT_BREAK_ANY_BIT = 3 + W_BUNDLE;
localparam SLOT_BREAK_D_MODE_BIT = 2 + W_BUNDLE;
localparam SLOT_ERR_BIT = 1 + W_BUNDLE;
localparam SLOT_PREDBRANCH_BIT = 0 + W_BUNDLE;
reg [3*W_SLOT-1:0] buf_contents;
reg [1:0] buf_level;
wire fetch_data_vld = !fifo_empty || (mem_data_vld && ~|ctr_flush_pending && !debug_mode);
wire [W_DATA-1:0] fetch_data = fifo_empty ? mem_data : fifo_rdata;
wire [1:0] fetch_data_hwvld = fifo_empty ? mem_data_hwvld : fifo_valid_hw[0];
wire fetch_bus_err = fifo_empty ? mem_or_pmp_err : fifo_err[0];
wire [1:0] fetch_break_any = fifo_empty ? mem_break_any : fifo_break_any[0];
wire [1:0] fetch_break_d_mode = fifo_empty ? mem_break_d_mode : fifo_break_d_mode[0];
wire [1:0] fetch_predbranch = fifo_empty ? mem_data_predbranch : fifo_predbranch[0];
wire [W_SLOT-1:0] fetch_contents_hw1 = {
fetch_break_any[1],
fetch_break_d_mode[1],
fetch_bus_err,
fetch_predbranch[1],
fetch_data[W_BUNDLE +: W_BUNDLE]
};
wire [W_SLOT-1:0] fetch_contents_hw0 = {
fetch_break_any[0],
fetch_break_d_mode[0],
fetch_bus_err,
fetch_predbranch[0],
fetch_data[0 +: W_BUNDLE]
};
wire [2*W_SLOT-1:0] fetch_contents_aligned = {
fetch_contents_hw1,
fetch_data_hwvld[0] || ~|EXTENSION_C ? fetch_contents_hw0 : fetch_contents_hw1
};
// Shift not-yet-used contents down to backfill D's consumption. We don't care
// about anything which is invalid or will be overlaid with fresh data, so
// choose these values in a way that minimises muxes.
wire [3*W_SLOT-1:0] buf_shifted =
cir_use[1] ? {buf_contents[W_SLOT +: 2 * W_SLOT], buf_contents[2 * W_SLOT +: W_SLOT]} :
cir_use[0] && EXTENSION_C ? {buf_contents[2 * W_SLOT +: W_SLOT], buf_contents[W_SLOT +: 2 * W_SLOT]} :
buf_contents;
wire [1:0] level_next_no_fetch = buf_level - cir_use;
// Overlay fresh fetch data onto the shifted/recycled buffer contents. Again,
// if something won't be looked at, generate the cheapest possible garbage.
assign cir_room_for_fetch = level_next_no_fetch <= (|EXTENSION_C && ~&fetch_data_hwvld ? 2'h2 : 2'h1);
assign fifo_pop = cir_room_for_fetch && !fifo_empty;
wire [3*W_SLOT-1:0] buf_shifted_plus_fetch =
!cir_room_for_fetch ? buf_shifted :
level_next_no_fetch[1] && |EXTENSION_C ? {fetch_contents_aligned[0 +: W_SLOT], buf_shifted[0 +: 2 * W_SLOT]} :
level_next_no_fetch[0] && |EXTENSION_C ? {fetch_contents_aligned, buf_shifted[0 +: W_SLOT]} :
{buf_shifted[2 * W_SLOT +: W_SLOT], fetch_contents_aligned};
wire [1:0] fetch_fill_amount = cir_room_for_fetch && fetch_data_vld ? (
&fetch_data_hwvld || ~|EXTENSION_C ? 2'h2 : 2'h1
) : 2'h0;
wire [1:0] buf_level_next = {1'b1, |EXTENSION_C} & (
jump_now && cir_flush_behind ? (cir_is_32bit ? 2'h2 : 2'h1) :
jump_now ? 2'h0 : level_next_no_fetch + fetch_fill_amount
);
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
buf_level <= 2'h0;
cir_vld <= 2'h0;
// Mysterious reset value ensures address buses are zero in reset
// (see definition of d_addr_offs in hazard3_decode)
buf_contents <= {{3 * W_SLOT - 2{1'b0}}, 2'b11};
end else begin
buf_level <= buf_level_next;
cir_vld <= buf_level_next & ~(buf_level_next >> 1'b1);
buf_contents <= buf_shifted_plus_fetch;
end
end
`ifdef HAZARD3_ASSERTIONS
reg [1:0] prop_past_buf_level; // Workaround for weird non-constant $past reset issue
always @ (posedge clk) begin
if (!rst_n) begin
prop_past_buf_level <= 2'h0;
end else begin
prop_past_buf_level <= buf_level;
assert(cir_vld <= 2);
assert(cir_use <= cir_vld);
if (!jump_now) assert(buf_level_next >= level_next_no_fetch);
// We fetch 32 bits per cycle, max. If this happens it's due to negative overflow.
if (prop_past_buf_level == 2'h0)
assert(buf_level != 2'h3);
end
end
`endif
assign cir_err = {
buf_contents[1 * W_SLOT + SLOT_ERR_BIT],
buf_contents[0 * W_SLOT + SLOT_ERR_BIT]
};
assign cir_predbranch = {
buf_contents[1 * W_SLOT + SLOT_PREDBRANCH_BIT],
buf_contents[0 * W_SLOT + SLOT_PREDBRANCH_BIT]
};
assign cir_break_any = buf_contents[0 * W_SLOT + SLOT_BREAK_ANY_BIT] && |cir_vld;
assign cir_break_d_mode = buf_contents[0 * W_SLOT + SLOT_BREAK_D_MODE_BIT] && |cir_vld;
// ----------------------------------------------------------------------------
// Register number predecode
wire [31:0] next_instr = {
buf_shifted_plus_fetch[1 * W_SLOT +: W_BUNDLE],
buf_shifted_plus_fetch[0 * W_SLOT +: W_BUNDLE]
};
wire next_instr_is_32bit = next_instr[1:0] == 2'b11 || ~|EXTENSION_C;
wire [3:0] decomp_uop_step;
wire [3:0] uop_ctr = decomp_uop_step & {4{|EXTENSION_ZCMP}};
wire [4:0] zcmp_pushpop_rs2 =
uop_ctr == 4'h0 ? 5'd01 : // ra
uop_ctr == 4'h1 ? 5'd08 : // s0
uop_ctr == 4'h2 ? 5'd09 : // s1
5'd15 + {1'b0, uop_ctr} ; // s2-s11
wire [4:0] zcmp_pushpop_rs1 =
uop_ctr < 4'hd ? 5'd02 : // sp (addr base reg)
uop_ctr == 4'hd ? 5'd00 : // zero (clear a0)
uop_ctr == 4'he ? 5'd01 : // ra (ret)
5'd02 ; // sp (stack adj)
wire [4:0] zcmp_sa01_r1s = {|next_instr[9:8], ~|next_instr[9:8], next_instr[9:7]};
wire [4:0] zcmp_sa01_r2s = {|next_instr[4:3], ~|next_instr[4:3], next_instr[4:2]};
wire [4:0] zcmp_mvsa01_rs1 = {4'h5, uop_ctr[0]};
wire [4:0] zcmp_mva01s_rs1 = uop_ctr[0] ? zcmp_sa01_r2s : zcmp_sa01_r1s;
// "coarse" because the mapping of pair (x0, x1) -> (x0, x0) is not yet applied
wire [4:0] zilsd_rs2_coarse = { next_instr[24:21], df_lspair_phase_next ^ ~next_instr[15]};
wire [4:0] zclsd_sd_rs2_coarse = {2'b01, next_instr[4:3], df_lspair_phase_next ^ ~next_instr[7] };
wire [4:0] zclsd_sdsp_rs2_coarse = { next_instr[6:3], df_lspair_phase_next ^ 1'b1 };
always @ (*) begin
casez ({next_instr_is_32bit, |EXTENSION_ZCMP, next_instr[15:0]})
{1'b1, 1'bz, 16'bzzzzzzzzzzzzzzzz}: predecode_rs1_coarse = next_instr[19:15]; // 32-bit R, S, B formats
{1'b0, 1'bz, 16'b00zzzzzzzzzzzz00}: predecode_rs1_coarse = 5'd2; // c.addi4spn + don't care
{1'b0, 1'bz, 16'b0zzzzzzzzzzzzz01}: predecode_rs1_coarse = next_instr[11:7]; // c.addi, c.addi16sp + don't care (jal, li)
{1'b0, 1'bz, 16'bz1zzzzzzzzzzzz10}: predecode_rs1_coarse = 5'd2; // c.lwsp, c.swsp, c.ldsp, c.sdsp
{1'b0, 1'bz, 16'bz00zzzzzzzzzzz10}: predecode_rs1_coarse = next_instr[11:7]; // c.slli, c.mv, c.add
{1'b0, 1'b1, 16'b1011zzzzzzzzzz10}: predecode_rs1_coarse = zcmp_pushpop_rs1; // cm.push, cm.pop*
{1'b0, 1'b1, 16'b1010zzzzz0zzzz10}: predecode_rs1_coarse = zcmp_mvsa01_rs1; // cm.mvsa01
{1'b0, 1'b1, 16'b1010zzzzz1zzzz10}: predecode_rs1_coarse = zcmp_mva01s_rs1; // cm.mva01s
default: predecode_rs1_coarse = {2'b01, next_instr[9:7]};
endcase
casez ({next_instr_is_32bit, |EXTENSION_ZCMP, |EXTENSION_ZILSD, |EXTENSION_ZCLSD, next_instr[15:0]})
{1'b1, 1'bz, 1'b1, 1'bz, 16'bzz11zzzzz0z0zzzz}: predecode_rs2_coarse = zilsd_rs2_coarse; // ld, sd (Zilsd)
{1'b1, 1'bz, 1'b0, 1'bz, 16'bzz11zzzzz0z0zzzz}: predecode_rs2_coarse = next_instr[24:20]; // ld, sd (no Zilsd)
{1'b1, 1'bz, 1'bz, 1'bz, 16'bzz0zzzzzzzzzzzzz}: predecode_rs2_coarse = next_instr[24:20]; // (cover remaining 32-bit
{1'b1, 1'bz, 1'bz, 1'bz, 16'bzz10zzzzzzzzzzzz}: predecode_rs2_coarse = next_instr[24:20]; // patterns, without overlap)
{1'b1, 1'bz, 1'bz, 1'bz, 16'bzz11zzzzz1zzzzzz}: predecode_rs2_coarse = next_instr[24:20];
{1'b1, 1'bz, 1'bz, 1'bz, 16'bzz11zzzzz0z1zzzz}: predecode_rs2_coarse = next_instr[24:20];
{1'b0, 1'bz, 1'b1, 1'b1, 16'bzz1zzzzzzzzzzz00}: predecode_rs2_coarse = zclsd_sd_rs2_coarse;
{1'b0, 1'bz, 1'bz, 1'bz, 16'bzz0zzzzzzzzzzz10}: predecode_rs2_coarse = next_instr[6:2]; // c.add, c.swsp
{1'b0, 1'b1, 1'bz, 1'bz, 16'bz01zzzzzzzzzzz10}: predecode_rs2_coarse = zcmp_pushpop_rs2; // cm.push
{1'b0, 1'bz, 1'b1, 1'b1, 16'bz11zzzzzzzzzzz10}: predecode_rs2_coarse = zclsd_sdsp_rs2_coarse;
default: predecode_rs2_coarse = {2'b01, next_instr[4:2]};
endcase
// The "fine" predecode targets those instructions which either:
// - Have an implicit zero-register operand in their expanded form (e.g. c.beqz)
// - Do not have a register operand on that port, but rely on the port being 0
// We don't care about instructions which ignore the reg ports, e.g. ebreak
casez ({|EXTENSION_C, next_instr})
// -> addi rd, x0, imm:
{1'b1, 16'hzzzz, `RVOPC_C_LI}: predecode_rs1_fine = 5'd0;
{1'b1, 16'hzzzz, `RVOPC_C_MV}: begin
if (next_instr[6:2] == 5'd0) begin
// c.jr has rs1 as normal
predecode_rs1_fine = predecode_rs1_coarse;
end else begin
// -> add rd, x0, rs2:
predecode_rs1_fine = 5'd0;
end
end
default: predecode_rs1_fine = predecode_rs1_coarse;
endcase
casez ({|EXTENSION_C, |EXTENSION_ZILSD, |EXTENSION_ZCLSD, next_instr})
{1'b1, 1'bz, 1'bz, 16'hzzzz, `RVOPC_C_BEQZ}: predecode_rs2_fine = 5'd0; // -> beq rs1, x0, label
{1'b1, 1'bz, 1'bz, 16'hzzzz, `RVOPC_C_BNEZ}: predecode_rs2_fine = 5'd0; // -> bne rs1, x0, label
{1'b1, 1'b1, 1'bz, `RVOPC_SD }: predecode_rs2_fine = predecode_rs2_coarse & {5{|next_instr[24:21]}};
{1'b1, 1'b1, 1'b1, 16'hzzzz, `RVOPC_C_SDSP}: predecode_rs2_fine = predecode_rs2_coarse & {5{|next_instr[ 6: 3]}};
default: predecode_rs2_fine = predecode_rs2_coarse;
endcase
if (|EXTENSION_E) begin
predecode_rs1_coarse[4] = 1'b0;
predecode_rs2_coarse[4] = 1'b0;
predecode_rs1_fine[4] = 1'b0;
predecode_rs2_fine[4] = 1'b0;
end
end
// ----------------------------------------------------------------------------
// Instruction decompression
// Instructions are decompressed at the end of stage 1 (fetch data phase). On
// ASIC, where the register file is synthesised with muxes, this puts
// decompression somewhat in parallel with register file read, which uses
// approximately decoded regnums.
generate
if (~|EXTENSION_C) begin: no_decompress
// No decompression; instructions decoded directly from prefetch buffer
always @ (*) begin
cir = {
buf_contents[1 * W_SLOT +: W_BUNDLE],
buf_contents[0 * W_SLOT +: W_BUNDLE]
} | 32'd3;
cir_is_32bit = 1'b1;
cir_invalid_16bit = ~&buf_contents[1:0];
cir_is_uop = 1'b0;
cir_uop_nonfinal = 1'b0;
cir_uop_no_pc_update = 1'b0;
cir_uop_atomic = 1'b0;
end
assign decomp_uop_step = 4'h0;
end else begin: have_decompress
wire decomp_instr_is_32bit;
wire [31:0] decomp_instr_out;
wire decomp_is_uop;
wire decomp_is_final_uop;
wire decomp_uop_no_pc_update;
wire decomp_uop_atomic;
wire decomp_invalid;
wire first_uop = ~|decomp_uop_step;
// Ensure the first uop goes straight through, as it is registered into CIR:
wire uop_stall_non_first = first_uop ? ~|buf_level_next : uop_stall;
// Ensure the uop counter stops at 0 after rolling over once:
wire uop_stall_on_repeat = cir_is_uop && !cir_uop_nonfinal && ~|cir_use;
hazard3_instr_decompress #(
`include "hazard3_config_inst.vh"
) decomp (
.clk (clk),
.rst_n (rst_n),
.instr_in (next_instr),
.instr_is_32bit (decomp_instr_is_32bit),
.instr_out (decomp_instr_out),
.instr_out_is_uop (decomp_is_uop),
.instr_out_is_final_uop (decomp_is_final_uop),
.instr_out_uop_no_pc_update (decomp_uop_no_pc_update),
.instr_out_uop_atomic (decomp_uop_atomic),
.instr_out_uop_stall (uop_stall_non_first || uop_stall_on_repeat),
.instr_out_uop_clear (uop_clear),
.df_uop_step (decomp_uop_step),
.invalid (decomp_invalid)
);
wire cir_clken =
~|cir_vld || (!cir_vld[1] && &buf_contents[1:0]) ||
|cir_use || (|EXTENSION_ZCMP && cir_is_uop && !uop_stall) ||
(|EXTENSION_ZCMP && uop_clear);
wire cir_is_uop_next = decomp_is_uop && |buf_level_next;
wire cir_uop_nonfinal_next = cir_is_uop_next && !decomp_is_final_uop;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
cir <= 32'd3;
cir_is_32bit <= 1'b0;
cir_invalid_16bit <= 1'b0;
cir_is_uop <= 1'b0;
cir_uop_nonfinal <= 1'b0;
cir_uop_no_pc_update <= 1'b0;
cir_uop_atomic <= 1'b0;
end else if (cir_clken) begin
cir <= decomp_instr_out | 32'd3;
cir_is_32bit <= decomp_instr_is_32bit;
cir_invalid_16bit <= decomp_invalid;
cir_is_uop <= |EXTENSION_ZCMP && !uop_clear && cir_is_uop_next;
cir_uop_nonfinal <= |EXTENSION_ZCMP && !uop_clear && cir_uop_nonfinal_next;
cir_uop_no_pc_update <= |EXTENSION_ZCMP && !uop_clear && decomp_uop_no_pc_update;
cir_uop_atomic <= |EXTENSION_ZCMP && !uop_clear && decomp_uop_atomic;
end
end
end
endgenerate
assign cir_raw = {
buf_contents[1 * W_SLOT +: W_BUNDLE],
buf_contents[0 * W_SLOT +: W_BUNDLE]
};
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+478
View File
@@ -0,0 +1,478 @@
/*****************************************************************************\
| Copyright (C) 2021-2023 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
// Little instructions go in, big instructions come out
`default_nettype none
module hazard3_instr_decompress #(
`include "hazard3_config.vh"
) (
input wire clk,
input wire rst_n,
input wire [31:0] instr_in,
output reg instr_is_32bit,
output reg [31:0] instr_out,
// If instruction is a non-final uop, need to suppress PC update, and null
// the PC offset in the mepc address in stage 3.
output wire instr_out_is_uop,
output wire instr_out_is_final_uop,
output wire instr_out_uop_no_pc_update,
// Indicate instr_out is a uop from the noninterruptible part of a uop
// sequence. If one uop is noninterruptible, all following uops until the
// end of the sequence are also noninterruptible.
output wire instr_out_uop_atomic,
// Current ucode sequence is stalled on downstream execution
input wire instr_out_uop_stall,
input wire instr_out_uop_clear,
// To regnum decoder in frontend
output wire [3:0] df_uop_step,
output reg invalid
);
`include "rv_opcodes.vh"
localparam W_REGADDR = 5;
localparam PASSTHROUGH = ~|EXTENSION_C;
// Long-register formats: cr, ci, css
// Short-register formats: ciw, cl, cs, cb, cj
wire [W_REGADDR-1:0] rd_l = instr_in[11:7];
wire [W_REGADDR-1:0] rs1_l = instr_in[11:7];
wire [W_REGADDR-1:0] rs2_l = instr_in[6:2];
wire [W_REGADDR-1:0] rd_s = {2'b01, instr_in[4:2]};
wire [W_REGADDR-1:0] rs1_s = {2'b01, instr_in[9:7]};
wire [W_REGADDR-1:0] rs2_s = {2'b01, instr_in[4:2]};
// Mapping of cx -> x immediate formats (we are *expanding* instructions, not
// decoding them):
wire [31:0] imm_ci = {
{7{instr_in[12]}},
instr_in[6:2],
20'h00000
};
wire [31:0] imm_cj = {
instr_in[12],
instr_in[8],
instr_in[10:9],
instr_in[6],
instr_in[7],
instr_in[2],
instr_in[11],
instr_in[5:3],
{9{instr_in[12]}},
12'h000
};
wire [31:0] imm_cb ={
{4{instr_in[12]}},
instr_in[6:5],
instr_in[2],
13'h0000,
instr_in[11:10],
instr_in[4:3],
instr_in[12],
7'h00
};
wire [31:0] imm_c_lb = {
10'h0,
instr_in[5],
instr_in[6],
20'h00000
};
wire [31:0] imm_c_lh = {
10'h000,
instr_in[5],
1'b0,
20'h00000
};
function [31:0] rfmt_rd; input [4:0] rd; begin rfmt_rd = {20'h00000, rd, 7'h00}; end endfunction
function [31:0] rfmt_rs1; input [4:0] rs1; begin rfmt_rs1 = {12'h000, rs1, 15'h0000}; end endfunction
function [31:0] rfmt_rs2; input [4:0] rs2; begin rfmt_rs2 = {7'h00, rs2, 20'h00000}; end endfunction
// ----------------------------------------------------------------------------
// Push/pop and friends
// The longest uop sequence is a maximal cm.popretz:
//
// - 13x lw (counter = 0..12)
// - 1x addi to set a0 to zero (counter = 13 ) < atomic section
// - 1x jalr to jump through ra (counter = 14 ) < atomic section
// - 1x addi to adjust sp (counter = 15 ) < atomic section
wire [3:0] uop_ctr;
reg [3:0] uop_ctr_nxt_in_seq;
reg in_uop_seq;
reg uop_no_pc_update;
wire zcmp_is_pushpop = instr_in[12];
wire uop_seq_end = |EXTENSION_ZCMP && (zcmp_is_pushpop ? uop_ctr == 4'hf : uop_ctr[0]);
wire uop_atomic = |EXTENSION_ZCMP && (zcmp_is_pushpop ? uop_ctr >= 4'he : uop_ctr[0]);
wire [3:0] uop_ctr_nxt =
instr_out_uop_clear ? 4'h0 :
instr_out_uop_stall ? uop_ctr : uop_ctr_nxt_in_seq;
assign instr_out_is_uop = in_uop_seq;
assign instr_out_is_final_uop = uop_seq_end;
assign instr_out_uop_atomic = uop_atomic;
assign instr_out_uop_no_pc_update = uop_no_pc_update;
assign df_uop_step = uop_ctr;
// The offset from current sp value to the lowest-addressed saved register, +64.
wire [3:0] zcmp_rlist = instr_in[7:4];
wire [3:0] zcmp_n_regs = zcmp_rlist == 4'hf ? 4'hd : zcmp_rlist - 4'h3;
wire zcmp_rlist_invalid = zcmp_rlist < 4'h4 || (|EXTENSION_E && zcmp_rlist > 4'h6);
wire [11:0] zcmp_stack_adj_base =
zcmp_rlist == 4'hf ? 12'h040 :
zcmp_rlist >= 4'hc ? 12'h030 :
zcmp_rlist >= 4'h8 ? 12'h020 : 12'h010;
wire [11:0] zcmp_stack_adj = zcmp_stack_adj_base + {6'h00, instr_in[3:2], 4'h0};
// Note we perform all load/stores before moving the stack pointer.
wire [11:0] zcmp_stack_lw_offset = -{6'h00, {zcmp_n_regs - uop_ctr}, 2'h0} + zcmp_stack_adj;
wire [11:0] zcmp_stack_sw_offset = -{6'h00, {zcmp_n_regs - uop_ctr}, 2'h0};
wire [4:0] zcmp_ls_reg =
uop_ctr == 4'h0 ? 5'd01 : // ra
uop_ctr == 4'h1 ? 5'd08 : // s0
uop_ctr == 4'h2 ? 5'd09 : // s1
5'd15 + {1'b0, uop_ctr}; // s2-s11 (s2 == x18)
wire [31:0] zcmp_push_sw_instr = `RVOPC_NOZ_SW | rfmt_rs1(5'd2) | rfmt_rs2(zcmp_ls_reg) | {
zcmp_stack_sw_offset[11:5], 13'h0000, zcmp_stack_sw_offset[4:0], 7'h00
};
wire [31:0] zcmp_pop_lw_instr = `RVOPC_NOZ_LW | rfmt_rd(zcmp_ls_reg) | rfmt_rs1(5'd2)| {
zcmp_stack_lw_offset[11:0], 20'h00000
};
wire [31:0] zcmp_push_stack_adj_instr = `RVOPC_NOZ_ADDI | rfmt_rd(5'd2) | rfmt_rs1(5'd2) | {
-zcmp_stack_adj,
20'h00000
};
wire [31:0] zcmp_pop_stack_adj_instr = `RVOPC_NOZ_ADDI | rfmt_rd(5'd2) | rfmt_rs1(5'd2) | {
zcmp_stack_adj,
20'h00000
};
wire [4:0] zcmp_sa01_r1s = {|instr_in[9:8], ~|instr_in[9:8], instr_in[9:7]};
wire [4:0] zcmp_sa01_r2s = {|instr_in[4:3], ~|instr_in[4:3], instr_in[4:2]};
wire zcmp_sa01_invalid = |EXTENSION_E && |{instr_in[9:8], instr_in[4:3]};
// ----------------------------------------------------------------------------
generate
if (PASSTHROUGH) begin: instr_passthrough
always @ (*) begin
instr_is_32bit = 1'b1;
instr_out = instr_in;
invalid = 1'b0;
end
end else begin: instr_decompress
always @ (*) begin
if (instr_in[1:0] == 2'b11) begin
instr_is_32bit = 1'b1;
instr_out = instr_in;
invalid = 1'b0;
in_uop_seq = 1'b0;
uop_no_pc_update = 1'b0;
uop_ctr_nxt_in_seq = uop_ctr;
end else begin
instr_is_32bit = 1'b0;
instr_out = 32'd0;
invalid = 1'b0;
in_uop_seq = 1'b0;
uop_no_pc_update = 1'b0;
uop_ctr_nxt_in_seq = uop_ctr;
casez (instr_in[15:0])
`RVOPC_C_ADDI4SPN: begin
instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(rd_s) | rfmt_rs1(5'd2)
| {2'h0, instr_in[10:7], instr_in[12:11], instr_in[5], instr_in[6], 2'b00, 20'h00000};
invalid = ~|instr_in[12:2]; // Always-invalid all-zeroes instruction
end
`RVOPC_C_LW: instr_out = `RVOPC_NOZ_LW | rfmt_rd(rd_s) | rfmt_rs1(rs1_s)
| {5'h00, instr_in[5], instr_in[12:10], instr_in[6], 2'b00, 20'h00000};
`RVOPC_C_SW: instr_out = `RVOPC_NOZ_SW | rfmt_rs2(rs2_s) | rfmt_rs1(rs1_s)
| {5'h00, instr_in[5], instr_in[12], 13'h0000, instr_in[11:10], instr_in[6], 2'b00, 7'h00};
`RVOPC_C_ADDI: instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(rd_l) | rfmt_rs1(rs1_l) | imm_ci;
`RVOPC_C_JAL: instr_out = `RVOPC_NOZ_JAL | rfmt_rd(5'd1) | imm_cj;
`RVOPC_C_J: instr_out = `RVOPC_NOZ_JAL | rfmt_rd(5'd0) | imm_cj;
`RVOPC_C_LI: instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(rd_l) | imm_ci;
`RVOPC_C_LUI: begin
if (rd_l == 5'd2) begin
// addi16sp
instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(5'd2) | rfmt_rs1(5'd2) |
{{3{instr_in[12]}}, instr_in[4:3], instr_in[5], instr_in[2], instr_in[6], 24'h000000};
end else begin
instr_out = `RVOPC_NOZ_LUI | rfmt_rd(rd_l) | {{15{instr_in[12]}}, instr_in[6:2], 12'h000};
end
invalid = ~|{instr_in[12], instr_in[6:2]}; // RESERVED if imm == 0
end
`RVOPC_C_SLLI: instr_out = `RVOPC_NOZ_SLLI | rfmt_rd(rs1_l) | rfmt_rs1(rs1_l) | imm_ci;
`RVOPC_C_SRAI: instr_out = `RVOPC_NOZ_SRAI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | imm_ci;
`RVOPC_C_SRLI: instr_out = `RVOPC_NOZ_SRLI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | imm_ci;
`RVOPC_C_ANDI: instr_out = `RVOPC_NOZ_ANDI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | imm_ci;
`RVOPC_C_AND: instr_out = `RVOPC_NOZ_AND | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s);
`RVOPC_C_OR: instr_out = `RVOPC_NOZ_OR | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s);
`RVOPC_C_XOR: instr_out = `RVOPC_NOZ_XOR | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s);
`RVOPC_C_SUB: instr_out = `RVOPC_NOZ_SUB | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s);
`RVOPC_C_ADD: begin
if (|rs2_l) begin
instr_out = `RVOPC_NOZ_ADD | rfmt_rd(rd_l) | rfmt_rs1(rs1_l) | rfmt_rs2(rs2_l);
end else if (|rs1_l) begin // jalr
instr_out = `RVOPC_NOZ_JALR | rfmt_rd(5'd1) | rfmt_rs1(rs1_l);
end else begin // ebreak
instr_out = `RVOPC_NOZ_EBREAK;
end
end
`RVOPC_C_MV: begin
if (|rs2_l) begin // mv
instr_out = `RVOPC_NOZ_ADD | rfmt_rd(rd_l) | rfmt_rs2(rs2_l);
end else begin // jr
instr_out = `RVOPC_NOZ_JALR | rfmt_rs1(rs1_l);
invalid = ~|rs1_l; // RESERVED
end
end
`RVOPC_C_LWSP: begin
instr_out = `RVOPC_NOZ_LW | rfmt_rd(rd_l) | rfmt_rs1(5'd2) |
{4'h0, instr_in[3:2], instr_in[12], instr_in[6:4], 2'b00, 20'h00000};
invalid = ~|rd_l; // RESERVED
end
`RVOPC_C_SWSP: instr_out = `RVOPC_NOZ_SW | rfmt_rs2(rs2_l) | rfmt_rs1(5'd2)
| {4'h0, instr_in[8:7], instr_in[12], 13'h0000, instr_in[11:9], 2'b00, 7'h00};
`RVOPC_C_BEQZ: instr_out = `RVOPC_NOZ_BEQ | rfmt_rs1(rs1_s) | imm_cb;
`RVOPC_C_BNEZ: instr_out = `RVOPC_NOZ_BNE | rfmt_rs1(rs1_s) | imm_cb;
// Optional Zcb instructions:
`RVOPC_C_LBU: begin
instr_out = `RVOPC_NOZ_LBU | rfmt_rd(rd_s) | rfmt_rs1(rs1_s) | imm_c_lb;
invalid = ~|EXTENSION_ZCB;
end
`RVOPC_C_LHU: begin
instr_out = `RVOPC_NOZ_LHU | rfmt_rd(rd_s) | rfmt_rs1(rs1_s) | imm_c_lh;
invalid = ~|EXTENSION_ZCB;
end
`RVOPC_C_LH: begin
instr_out = `RVOPC_NOZ_LH | rfmt_rd(rd_s) | rfmt_rs1(rs1_s) | imm_c_lh;
invalid = ~|EXTENSION_ZCB;
end
`RVOPC_C_SB: begin
instr_out = `RVOPC_NOZ_SB | rfmt_rs2(rd_s) | rfmt_rs1(rs1_s) | imm_c_lb >> 13;
invalid = ~|EXTENSION_ZCB;
end
`RVOPC_C_SH: begin
instr_out = `RVOPC_NOZ_SH | rfmt_rs2(rd_s) | rfmt_rs1(rs1_s) | imm_c_lh >> 13;
invalid = ~|EXTENSION_ZCB;
end
`RVOPC_C_ZEXT_B: begin
instr_out = `RVOPC_NOZ_ANDI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | 32'h0ff00000;
invalid = ~|EXTENSION_ZCB;
end
`RVOPC_C_SEXT_B: begin
instr_out = `RVOPC_NOZ_SEXT_B | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s);
invalid = ~|EXTENSION_ZCB || ~|EXTENSION_ZBB;
end
`RVOPC_C_ZEXT_H: begin
instr_out = `RVOPC_NOZ_ZEXT_H | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s);
invalid = ~|EXTENSION_ZCB || ~|EXTENSION_ZBB;
end
`RVOPC_C_SEXT_H: begin
instr_out = `RVOPC_NOZ_SEXT_H | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s);
invalid = ~|EXTENSION_ZCB || ~|EXTENSION_ZBB;
end
`RVOPC_C_NOT: begin
instr_out = `RVOPC_NOZ_XORI | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | 32'hfff00000;
invalid = ~|EXTENSION_ZCB;
end
`RVOPC_C_MUL: begin
instr_out = `RVOPC_NOZ_MUL | rfmt_rd(rs1_s) | rfmt_rs1(rs1_s) | rfmt_rs2(rs2_s);
invalid = ~|EXTENSION_ZCB || ~|EXTENSION_M;
end
// Optional Zclsd instructions:
`RVOPC_C_LD: begin
instr_out = `RVOPC_NOZ_LD | rfmt_rd(rd_s) | rfmt_rs1(rs1_s)
| {4'h0, instr_in[6:5], instr_in[12:10], 3'b000, 20'h00000};
invalid = ~|EXTENSION_ZILSD || ~|EXTENSION_ZCLSD;
end
`RVOPC_C_SD: begin
instr_out = `RVOPC_NOZ_SD | rfmt_rs2(rs2_s) | rfmt_rs1(rs1_s)
| {4'h0, instr_in[6:5], instr_in[12], 13'h0000, instr_in[11:10], 3'b000, 7'h00};
invalid = ~|EXTENSION_ZILSD || ~|EXTENSION_ZCLSD;
end
`RVOPC_C_LDSP: begin
instr_out = `RVOPC_NOZ_LD | rfmt_rd(rd_l) | rfmt_rs1(5'd2) |
{3'h0, instr_in[4:2], instr_in[12], instr_in[6:5], 3'b000, 20'h00000};
invalid = ~|EXTENSION_ZILSD || ~|EXTENSION_ZCLSD || ~|rd_l; // RESERVED
end
`RVOPC_C_SDSP: begin
instr_out = `RVOPC_NOZ_SD | rfmt_rs2(rs2_l) | rfmt_rs1(5'd2)
| {3'h0, instr_in[9:7], instr_in[12], 13'h0000, instr_in[11:10], 3'b000, 7'h00};
invalid = ~|EXTENSION_ZILSD || ~|EXTENSION_ZCLSD;
end
// Optional Zcmp instructions:
`RVOPC_CM_PUSH: if (~|EXTENSION_ZCMP || zcmp_rlist_invalid) begin
invalid = 1'b1;
end else if (uop_ctr == 4'hf) begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = 4'h0;
instr_out = zcmp_push_stack_adj_instr;
end else begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = uop_ctr + 4'h1;
instr_out = zcmp_push_sw_instr;
uop_no_pc_update = 1'b1;
if (uop_ctr_nxt_in_seq == zcmp_n_regs) begin
uop_ctr_nxt_in_seq = 4'hf;
end
end
`RVOPC_CM_POP: if (~|EXTENSION_ZCMP || zcmp_rlist_invalid) begin
invalid = 1'b1;
end else if (uop_ctr == 4'hf) begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = 4'h0;
instr_out = zcmp_pop_stack_adj_instr;
end else begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = uop_ctr + 4'h1;
uop_no_pc_update = 1'b1;
instr_out = zcmp_pop_lw_instr;
if (uop_ctr_nxt_in_seq == zcmp_n_regs) begin
uop_ctr_nxt_in_seq = 4'hf;
end
end
`RVOPC_CM_POPRET: if (~|EXTENSION_ZCMP || zcmp_rlist_invalid) begin
invalid = 1'b1;
end else if (uop_ctr == 4'he) begin
// Note although this is only the first instruction in the uninterruptible sequence,
// we mark this instruction as uninterruptible: there is some special case logic to
// allow this jump to execute without flushing the final stack adjust uop, which can
// cause the wrong exception PC to be sampled if this uop is interrupted.
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = uop_ctr + 4'h1;
instr_out = `RVOPC_NOZ_JALR | rfmt_rs1(5'd1);
end else if (uop_ctr == 4'hf) begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = 4'h0;
uop_no_pc_update = 1'b1;
instr_out = zcmp_pop_stack_adj_instr;
end else begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = uop_ctr + 4'h1;
instr_out = zcmp_pop_lw_instr;
uop_no_pc_update = 1'b1;
if (uop_ctr_nxt_in_seq == zcmp_n_regs) begin
uop_ctr_nxt_in_seq = 4'he;
end
end
`RVOPC_CM_POPRETZ: if (~|EXTENSION_ZCMP || zcmp_rlist_invalid) begin
invalid = 1'b1;
end else if (uop_ctr == 4'hd) begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = uop_ctr + 4'h1;
uop_no_pc_update = 1'b1;
instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(5'd10); // li a0, 0
end else if (uop_ctr == 4'he) begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = uop_ctr + 4'h1;
instr_out = `RVOPC_NOZ_JALR | rfmt_rs1(5'd1);
end else if (uop_ctr == 4'hf) begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = 4'h0;
uop_no_pc_update = 1'b1;
instr_out = zcmp_pop_stack_adj_instr;
end else begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = uop_ctr + 4'h1;
uop_no_pc_update = 1'b1;
instr_out = zcmp_pop_lw_instr;
if (uop_ctr_nxt_in_seq == zcmp_n_regs) begin
uop_ctr_nxt_in_seq = 4'hd;
end
end
`RVOPC_CM_MVSA01: if (~|EXTENSION_ZCMP || zcmp_sa01_invalid) begin
invalid = 1'b1;
end else if (uop_ctr == 4'h0) begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = uop_ctr + 4'h1;
uop_no_pc_update = 1'b1;
instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(zcmp_sa01_r1s) | rfmt_rs1(5'd10);
end else begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = 4'h0;
instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(zcmp_sa01_r2s) | rfmt_rs1(5'd11);
end
`RVOPC_CM_MVA01S: if (~|EXTENSION_ZCMP || zcmp_sa01_invalid) begin
invalid = 1'b1;
end else if (uop_ctr == 4'h0) begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = uop_ctr + 4'h1;
uop_no_pc_update = 1'b1;
instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(5'd10) | rfmt_rs1(zcmp_sa01_r1s);
end else begin
in_uop_seq = 1'b1;
uop_ctr_nxt_in_seq = 4'h0;
instr_out = `RVOPC_NOZ_ADDI | rfmt_rd(5'd11) | rfmt_rs1(zcmp_sa01_r2s);
end
default: invalid = 1'b1;
endcase
end
end
end
endgenerate
generate
if (EXTENSION_ZCMP) begin: have_uop_ctr
reg [3:0] uop_ctr_r;
assign uop_ctr = uop_ctr_r;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
uop_ctr_r <= 4'h0;
end else begin
uop_ctr_r <= uop_ctr_nxt;
`ifdef HAZARD3_ASSERTIONS
assert(in_uop_seq || uop_ctr_r == 4'h0);
assert(in_uop_seq || zcmp_ls_reg == 5'h01);
assert(in_uop_seq || !uop_atomic);
assert(in_uop_seq || !uop_no_pc_update);
if (uop_seq_end) begin
assert(in_uop_seq);
assert(instr_out_uop_stall || uop_ctr_nxt == 4'h0);
end
`endif
end
end
end else begin: no_uop_ctr
assign uop_ctr = 4'h0;
end
endgenerate
endmodule
`ifndef YOSYS
`default_nettype wire
`endif
+327
View File
@@ -0,0 +1,327 @@
/*****************************************************************************\
| Copyright (C) 2022 Luke Wren |
| SPDX-License-Identifier: Apache-2.0 |
\*****************************************************************************/
`default_nettype none
// Hazard3 interrupt controller. Support for up to 512 external interrupt
// lines, with up to 16 levels of preemption.
module hazard3_irq_ctrl #(
`include "hazard3_config.vh"
) (
input wire clk,
input wire clk_always_on,
input wire rst_n,
// CSR interface
input wire [11:0] addr,
input wire [1:0] wtype,
input wire wen_m_mode,
input wire ren_m_mode,
input wire [W_DATA-1:0] wdata_raw,
input wire [W_DATA-1:0] wdata,
output reg [W_DATA-1:0] rdata,
// Trap entry/exit signals for context update
input wire trapreg_update_enter,
input wire trapreg_update_exit,
input wire trap_entry_is_eirq,
// Interface for clearing and saving mie.mtie/msie via meicontext
output wire meicontext_clearts,
input wire mie_mtie,
input wire mie_msie,
// External IRQ inputs:
input wire [NUM_IRQS-1:0] irq,
// mip.meip:
output wire external_irq_pending
);
`include "hazard3_ops.vh"
`include "hazard3_csr_addr.vh"
localparam MAX_IRQS = 512;
localparam [3:0] IRQ_PRIORITY_MASK = ~(4'hf >> IRQ_PRIORITY_BITS);
localparam W_IRQ_INDEX = $clog2(MAX_IRQS);
// ----------------------------------------------------------------------------
// IRQ input flops
// Register external IRQ signals (mainly to avoid a through-path from IRQs to
// bus request signals). Always clocked, as it's used to generate a wakeup.
// Input registers can be removed on a per-IRQ basis, but this should be done
// with care as it does create a through-path from the IRQ to the bus.
wire [NUM_IRQS-1:0] irq_r;
genvar g;
generate
for (g = 0; g < NUM_IRQS; g = g + 1) begin: irq_reg_loop
if (IRQ_INPUT_BYPASS[g]) begin: no_reg
assign irq_r[g] = irq[g];
end else begin: have_reg
reg q;
always @ (posedge clk_always_on or negedge rst_n) begin
if (!rst_n) begin
q <= 1'b0;
end else begin
q <= irq[g];
end
end
assign irq_r[g] = q;
end
end
endgenerate
// ----------------------------------------------------------------------------
// CSR write
// Assigned later:
wire [W_IRQ_INDEX-1:0] meinext_irq;
wire meinext_noirq;
reg [3:0] eirq_highest_priority;
// Interrupt array registers:
reg [NUM_IRQS-1:0] meiea;
reg [NUM_IRQS-1:0] meifa;
reg [4*NUM_IRQS-1:0] meipra;
// Padded vectors for CSR readout
wire [MAX_IRQS-1:0] meiea_rdata = {{MAX_IRQS-NUM_IRQS{1'b0}}, meiea};
wire [MAX_IRQS-1:0] meifa_rdata = {{MAX_IRQS-NUM_IRQS{1'b0}}, meifa};
wire [4*MAX_IRQS-1:0] meipra_rdata = {{4*(MAX_IRQS-NUM_IRQS){1'b0}}, meipra};
always @ (posedge clk or negedge rst_n) begin: update_irq_reg_arrays
reg signed [31:0] i;
if (!rst_n) begin
meiea <= {NUM_IRQS{1'b0}};
meifa <= {NUM_IRQS{1'b0}};
meipra <= {4*NUM_IRQS{1'b0}};
end else begin
for (i = 0; i < NUM_IRQS; i = i + 1) begin
// CSR write update. Note raw wdata is used for array indexing --
// necessary for correctness, and also avoid a loop with rdata.
if (wen_m_mode && addr == MEIEA && $signed(wdata_raw[4:0]) == i[W_IRQ_INDEX-1:4]) begin
meiea[i] <= wdata[16 + (i % 16)];
end
if (wen_m_mode && addr == MEIFA && $signed(wdata_raw[4:0]) == i[W_IRQ_INDEX-1:4]) begin
meifa[i] <= wdata[16 + (i % 16)];
end
if (wen_m_mode && addr == MEIPRA && $signed(wdata_raw[6:0]) == i[W_IRQ_INDEX-1:2]) begin
meipra[4 * i +: 4] <= wdata[16 + 4 * (i % 4) +: 4] & IRQ_PRIORITY_MASK;
end
// Clear IRQ force when the corresponding IRQ is sampled from meinext
// (so that an IRQ can be posted *once* without modifying the ISR source)
if (meinext_irq == i[W_IRQ_INDEX-1:0] && ren_m_mode && addr == MEINEXT && !meinext_noirq) begin
meifa[i[$clog2(NUM_IRQS)-1:0]] <= 1'b0;
end
end
end
end
reg [3:0] meicontext_pppreempt;
reg [3:0] meicontext_ppreempt;
reg [4:0] meicontext_preempt;
reg meicontext_noirq;
reg [W_IRQ_INDEX-1:0] meicontext_irq;
reg meicontext_mreteirq;
wire [4:0] preempt_level_next = meinext_noirq ? 5'h10 : (
(5'd1 << (4 - IRQ_PRIORITY_BITS)) + {1'b0, eirq_highest_priority}
);
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
meicontext_pppreempt <= 4'h0;
meicontext_ppreempt <= 4'h0;
meicontext_preempt <= 5'h0;
meicontext_noirq <= 1'b1;
meicontext_irq <= {W_IRQ_INDEX{1'b0}};
meicontext_mreteirq <= 1'b0;
end else if (trapreg_update_enter) begin
if (trap_entry_is_eirq) begin
// Priority save. Note the MSB of preempt needn't be saved since,
// when it is set, preemption is impossible, so we won't be here.
meicontext_pppreempt <= meicontext_ppreempt & IRQ_PRIORITY_MASK;
meicontext_ppreempt <= meicontext_preempt[3:0] & IRQ_PRIORITY_MASK;
// Setting preempt isn't strictly necessary, since an updating read
// of meinext ought to be performed before re-enabling IRQs via
// mstatus.mie, but it seems the least surprising thing to do:
meicontext_preempt <= preempt_level_next & {1'b1, IRQ_PRIORITY_MASK};
meicontext_mreteirq <= 1'b1;
end else begin
meicontext_mreteirq <= 1'b0;
end
end else if (trapreg_update_exit) begin
meicontext_mreteirq <= 1'b0;
if (meicontext_mreteirq) begin
// Priority restore
meicontext_pppreempt <= 4'h0;
meicontext_ppreempt <= meicontext_pppreempt & IRQ_PRIORITY_MASK;
meicontext_preempt <= {1'b0, meicontext_ppreempt & IRQ_PRIORITY_MASK};
end
end else if (wen_m_mode && addr == MEICONTEXT) begin
meicontext_pppreempt <= wdata[31:28] & IRQ_PRIORITY_MASK;
meicontext_ppreempt <= wdata[27:24] & IRQ_PRIORITY_MASK;
meicontext_preempt <= wdata[20:16] & {1'b1, IRQ_PRIORITY_MASK};
meicontext_noirq <= wdata[15];
meicontext_irq <= wdata[12:4];
meicontext_mreteirq <= wdata[0];
end else if (wen_m_mode && addr == MEINEXT && wdata[0]) begin
// Interrupt has been sampled, with the update request set, so update
// the context (including preemption level) appropriately.
meicontext_preempt <= preempt_level_next & {1'b1, IRQ_PRIORITY_MASK};
meicontext_noirq <= meinext_noirq;
meicontext_irq <= meinext_irq;
end
end
assign meicontext_clearts = wen_m_mode && wtype != CSR_WTYPE_C && addr == MEICONTEXT && wdata_raw[1];
// ----------------------------------------------------------------------------
// External interrupt logic
// Trap request is asserted when there is an interrupt at or above our current
// preemption level. meinext displays interrupts at or above our *previous*
// preemption level: this masking helps avoid re-taking IRQs in frames that you
// have preempted.
wire [NUM_IRQS-1:0] meipa = irq_r | meifa;
wire [MAX_IRQS-1:0] meipa_rdata = {{MAX_IRQS-NUM_IRQS{1'b0}}, meipa};
reg [NUM_IRQS-1:0] eirq_active_above_preempt;
reg [NUM_IRQS-1:0] eirq_active_above_ppreempt;
always @ (*) begin: eirq_compare
integer i;
for (i = 0; i < NUM_IRQS; i = i + 1) begin
eirq_active_above_preempt[i] = meipa[i] && meiea[i] && {1'b0, meipra[i * 4 +: 4]} >= meicontext_preempt;
eirq_active_above_ppreempt[i] = meipa[i] && meiea[i] && meipra[i * 4 +: 4] >= meicontext_ppreempt;
end
end
assign external_irq_pending = |eirq_active_above_preempt;
assign meinext_noirq = ~|eirq_active_above_ppreempt;
// Two things remaining to calculate:
//
// - What is the IRQ number of the highest-priority pending IRQ that is above
// meicontext.ppreempt
// - What is the priority of that IRQ
//
// In the second case we can relax the calculation to ignore ppreempt, since it
// only needs to be valid if such an IRQ exists. Currently we choose to reuse
// the same priority selector (possibly longer critpath while saving area), but
// we could use a second priority selector that ignores ppreempt masking.
wire [NUM_IRQS-1:0] highest_eirq_onehot;
wire [W_IRQ_INDEX-1:0] meinext_irq_unmasked;
hazard3_onehot_priority_dynamic #(
.W_REQ (NUM_IRQS),
.N_PRIORITIES (16),
.PRIORITY_HIGHEST_WINS (1),
.TIEBREAK_HIGHEST_WINS (0)
) eirq_priority_u (
.pri (meipra[4*NUM_IRQS-1:0] & {NUM_IRQS{IRQ_PRIORITY_MASK}}),
.req (eirq_active_above_ppreempt),
.gnt (highest_eirq_onehot)
);
always @ (*) begin: get_highest_eirq_priority
integer i;
eirq_highest_priority = 4'h0;
for (i = 0; i < NUM_IRQS; i = i + 1) begin
eirq_highest_priority = eirq_highest_priority | (
meipra[4 * i +: 4] & {4{highest_eirq_onehot[i]}}
);
end
end
wire [$clog2(NUM_IRQS)-1:0] meinext_irq_unmasked_nopad;
hazard3_onehot_encode #(
.W_REQ (NUM_IRQS)
) eirq_encode_u (
.req (highest_eirq_onehot),
.gnt (meinext_irq_unmasked_nopad)
);
generate
if ($clog2(NUM_IRQS) == $clog2(MAX_IRQS)) begin: encode_eirq_no_padding
assign meinext_irq_unmasked = meinext_irq_unmasked_nopad;
end else begin: encode_eirq_padded
assign meinext_irq_unmasked = {
{$clog2(MAX_IRQS) - $clog2(NUM_IRQS){1'b0}},
meinext_irq_unmasked_nopad
};
end
endgenerate
// It is unnecessary to mask meinext_irq based on meinext_noirq because:
// - The value of the CSR field is unimportant when noirq is set
// - There are no IRQ inputs to the priority selector when there
// are no IRQs, so result is already 0.
assign meinext_irq = meinext_irq_unmasked;
// ----------------------------------------------------------------------------
// CSR read
always @ (*) begin
rdata = {W_DATA{1'b0}};
case (addr)
MEIEA: rdata = {
meiea_rdata[wdata_raw[4:0] * 16 +: 16],
16'h0
};
MEIPA: rdata = {
meipa_rdata[wdata_raw[4:0] * 16 +: 16],
16'h0
};
MEIFA: rdata = {
meifa_rdata[wdata_raw[4:0] * 16 +: 16],
16'h0
};
MEIPRA: rdata = {
meipra_rdata[wdata_raw[6:0] * 16 +: 16],
16'h0
};
MEINEXT: rdata = {
meinext_noirq,
20'h0,
meinext_irq,
2'h0
};
MEICONTEXT: rdata = {
meicontext_pppreempt,
meicontext_ppreempt,
3'h0,
meicontext_preempt,
meicontext_noirq,
2'h0,
meicontext_irq,
mie_mtie && meicontext_clearts,
mie_msie && meicontext_clearts,
1'b0,
meicontext_mreteirq
};
default: rdata = {W_DATA{1'b0}};
endcase
end
endmodule
`ifndef YOSYS
`default_nettype wire
`endif

Some files were not shown because too many files have changed in this diff Show More