From ff440776bb928653f38c245ee7dfc4c87a389921 Mon Sep 17 00:00:00 2001 From: Olivier Muller Date: Mon, 3 Nov 2025 11:00:11 +0100 Subject: [PATCH] Squelette du projet --- .gitignore | 6 + .mill-jvm-opts | 1 + .mill-version | 1 + Makefile | 186 +++++++++ bench/chenillard_minimaliste.s | 15 + bench/chenillard_rotation.s | 23 + bench/compteur.s | 4 + bench/crt.S | 16 + bench/fmnsoc.h | 38 ++ bench/invaders.c | 438 ++++++++++++++++++++ bench/invaders.h | 49 +++ bench/link.ld | 47 +++ bench/lui.s | 7 + build.sc | 41 ++ common/ZzTop.xdc | 147 +++++++ common/bitstream.vivado.tcl | 61 +++ common/clock.xdc | 5 + common/config.gtkw | 76 ++++ common/fix_mem.awk | 27 ++ common/objtomem.awk | 73 ++++ common/parse_report.py | 98 +++++ common/programFPGA.vivado.tcl | 24 ++ common/programFPGA.xsdb.tcl | 4 + common/reportCriticalPaths.tcl | 28 ++ common/synthese.vivado.tcl | 35 ++ project/build.properties | 1 + project/plugins.sbt | 1 + src/main/resources/vsrc/clk_wiz.v | 200 +++++++++ src/main/resources/vsrc/dpram.v | 72 ++++ src/main/scala/common/EmitVerilog.scala | 52 +++ src/main/scala/projet/BusInterconnect.scala | 89 ++++ src/main/scala/projet/CLint.scala | 56 +++ src/main/scala/projet/ClockGen.scala | 64 +++ src/main/scala/projet/Compteur.scala | 35 ++ src/main/scala/projet/GeneSync.scala | 63 +++ src/main/scala/projet/IDMem.scala | 164 ++++++++ src/main/scala/projet/LedIp.scala | 46 ++ src/main/scala/projet/Rv32i.scala | 14 + src/main/scala/projet/VgaIp.scala | 40 ++ src/main/scala/projet/ZzTop.scala | 74 ++++ src/test/scala/projet/ZzTopSpec.scala | 101 +++++ 41 files changed, 2522 insertions(+) create mode 100644 .gitignore create mode 100644 .mill-jvm-opts create mode 100644 .mill-version create mode 100644 Makefile create mode 100644 bench/chenillard_minimaliste.s create mode 100644 bench/chenillard_rotation.s create mode 100644 bench/compteur.s create mode 100644 bench/crt.S create mode 100644 bench/fmnsoc.h create mode 100644 bench/invaders.c create mode 100644 bench/invaders.h create mode 100644 bench/link.ld create mode 100644 bench/lui.s create mode 100644 build.sc create mode 100644 common/ZzTop.xdc create mode 100644 common/bitstream.vivado.tcl create mode 100644 common/clock.xdc create mode 100644 common/config.gtkw create mode 100644 common/fix_mem.awk create mode 100755 common/objtomem.awk create mode 100644 common/parse_report.py create mode 100644 common/programFPGA.vivado.tcl create mode 100644 common/programFPGA.xsdb.tcl create mode 100644 common/reportCriticalPaths.tcl create mode 100644 common/synthese.vivado.tcl create mode 100644 project/build.properties create mode 100644 project/plugins.sbt create mode 100644 src/main/resources/vsrc/clk_wiz.v create mode 100644 src/main/resources/vsrc/dpram.v create mode 100644 src/main/scala/common/EmitVerilog.scala create mode 100644 src/main/scala/projet/BusInterconnect.scala create mode 100644 src/main/scala/projet/CLint.scala create mode 100644 src/main/scala/projet/ClockGen.scala create mode 100644 src/main/scala/projet/Compteur.scala create mode 100644 src/main/scala/projet/GeneSync.scala create mode 100644 src/main/scala/projet/IDMem.scala create mode 100644 src/main/scala/projet/LedIp.scala create mode 100644 src/main/scala/projet/Rv32i.scala create mode 100644 src/main/scala/projet/VgaIp.scala create mode 100644 src/main/scala/projet/ZzTop.scala create mode 100644 src/test/scala/projet/ZzTopSpec.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b527dcb --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +RTL +out +build +mill* +.prog* +bench/mem diff --git a/.mill-jvm-opts b/.mill-jvm-opts new file mode 100644 index 0000000..f5f4f57 --- /dev/null +++ b/.mill-jvm-opts @@ -0,0 +1 @@ +-Dchisel.project.root=${PWD} diff --git a/.mill-version b/.mill-version new file mode 100644 index 0000000..6799343 --- /dev/null +++ b/.mill-version @@ -0,0 +1 @@ +0.12.5 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4c68c9d --- /dev/null +++ b/Makefile @@ -0,0 +1,186 @@ +COMMON_DIR := common + +TOCLEAN = $(filter %log %.jou %.rpt %.mmi %.dcp %.csv\ + %.wdb vivado% updatemem% usage_statistics_webtalk%,$(wildcard *)) +TOCLEAN += $(wildcard _*) .Xil out RTL build download.bit + +# macro de verbosité +VERB := 0 +q = $(if $(filter 1, $(VERB)),$1,\ + $(if $2,@echo $2 && $1 > $3 2>&1, @$1)) + +# macro de vérification de variables d'environnement +check_vars = $(foreach v, $1, $(if $(filter undefined, $(origin $v)),\ + if [ "$v" = "XILINX_VIVADO" ]; then \ + echo "⚠️ Tapez : source /bigsoft/Xilinx/Vivado/2019.1/settings64.sh"; \ + exit 1; \ + else \ + echo "⚠️ ⚡⚡ Variable d'environnement $v manquante ⚡⚡⚠️ "; \ + $(MAKE) --no-print-directory help; \ + exit 1; \ + fi;)) + + +all: help + +help: + @awk 'BEGIN {FS = ":.*##!"; printf "Usage: make \033[32m\033[0m \ + [PROG=] \ + [CYCLES=durée de la simulation] [VERB=1 pour la verbosité]\ + \nCommandes par \033[36mcatégories :\n"} \ + /^[a-zA-Z0-9_-]+:.*##!/ { printf " \033[32m%-15s\033[0m %s\n", $$1, $$2 } \ + /^##@/ { printf "\n\033[36m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST) + + +.PHONY: synthese fpga clean compile autotest simulation + +############################################################################### + + +## Partie Compilation + +BENCH_DIR := bench +MEM_DIR := $(BENCH_DIR)/mem +SRC_SW := $(wildcard $(BENCH_DIR)/*.s) +SRC_SW += $(wildcard $(BENCH_DIR)/*.c) # Contient tous les .s et .c du projet +MEM = $(addprefix $(MEM_DIR)/, $(addsuffix .mem, $(basename $(notdir $(SRC_SW))))) # Tous les .mem du projet +PREFIX := $(shell command -v riscv64-unknown-elf-gcc >/dev/null 2>&1 && echo riscv64-unknown-elf- || echo /matieres/3MMFMN/riscv32/bin/riscv32-unknown-elf-) +CC := $(PREFIX)gcc +OBJDUMP := $(PREFIX)objdump +OBTOMEM := common/objtomem.awk + +## Flags +ASFLAGS :=-march=rv32i -mabi=ilp32 -ffreestanding -nostdlib -T $(BENCH_DIR)/link.ld +CFLAGS :=$(ASFLAGS) +ELFFLAGS :=$(ASFLAGS) $(MEM_DIR)/crt.o -Os -fno-unroll-loops +ODFLAGS :=-j .text -j .rodata -j .data -s + +.PRECIOUS: $(MEM_DIR)/%.elf + +compile: $(MEM) +$(MEM_DIR): + @mkdir -p $@ +$(MEM_DIR)/crt.o: $(BENCH_DIR)/crt.S |$(MEM_DIR) + $(call q,\ + $(CC) $(CFLAGS) -c $< -o $@ , " Compilation crt",$@.log) +$(MEM_DIR)/%.elf: $(BENCH_DIR)/%.c $(MEM_DIR)/crt.o|$(MEM_DIR) + $(call q,\ + $(CC) $(ELFFLAGS) $< -o $@ , " Compilation $<",$@.log) +$(MEM_DIR)/%.elf: $(BENCH_DIR)/%.s |$(MEM_DIR) + $(call q,\ + $(CC) $(CFLAGS) -o $@ $<, " Compilation $<",$@.log) +$(MEM_DIR)/%.mem: $(MEM_DIR)/%.elf |$(MEM_DIR) + $(call q,\ + $(OBJDUMP) $(ODFLAGS) $< | awk -f $(OBTOMEM) | awk -f common/fix_mem.awk," Génération de $@",$@) + + +############################################################################### + + +##@ Simulation + +TOP := ZzTop +TOP_REP := projet +PATH_SRC := src/main/scala/$(TOP_REP) +TOP_TEST := $(addsuffix Spec, $(TOP)) + +CYCLES=500 +# Pour filtrer les tests dans une simulation +ifdef PROG + FILTRE_TEST:= -- -z "$(PROG)" +endif + +mill: + @curl -L https://raw.githubusercontent.com/lefou/millw/0.4.11/millw > mill + @chmod +x mill + +autotest: mill compile ##! Lance la simulation automatique pour tous les tests ou juste celui fourni dans PROG + @PROOT=$(PWD) ./mill TPchisel.test.testOnly $(TOP_REP).$(TOP_TEST) $(FILTRE_TEST) + +simulation: compile ##! Lance gtkwave sur le test fourni dans PROG + @$(call check_vars,PROG) + @PROOT=$(PWD) PROG=$(PROG) CYCLES=$(CYCLES) ./mill TPchisel.test.testOnly $(TOP_REP).$(TOP_TEST) -- -DemitVcd=1 -z "gtkwave" + @gtkwave -a common/config.gtkw ./build/chiselsim/ZzTopSpec/Simulation-pour-gtkwave/workdir-verilator/trace.vcd + + +############################################################################### + + +##@ Passage sur carte + +SRC := $(wildcard $(PATH_SRC)/*.scala) +SRC += $(wildcard src/main/resources/vsrc/*.v) +FPGA := xc7z010-clg400-1 +SRC_SV := RTL/$(TOP_REP).$(TOP)/$(TOP).sv + + +XILINX_PREFIX := $(XILINX_VIVADO)/bin/ +MEM_FILE:= $(MEM_DIR)/$(PROG).mem +MMI_FILE:=$(TOP).mmi +MEM_ID=$(shell grep -oP 'InstPath="\K[^"]+' $(MMI_FILE)) +PARAM = $(MEM_FILE) false # Variable pour passer des parametres au générateur SV + +.PRECIOUS: golden.bit +# Créer un hash unique de la variable +PROG_HASH := $(shell echo "$(PROG)" | md5sum | cut -d' ' -f1) + +RTL: $(SRC_SV) ##! Génération du SystemVerilog + +$(SRC_SV): mill $(SRC) + $(call q, ./mill TPchisel.runMain common.EmitModule $(TOP_REP).$(TOP) $(PARAM) \ + , " Génération du SystemVerilog",/dev/null) + +$(TOP)_utilization.rpt $(TOP)_timing.rpt $(TOP)_summary.rpt: $(COMMON_DIR)/synthese.vivado.tcl RTL + @$(call check_vars,XILINX_VIVADO) + $(call q, \ + $(XILINX_PREFIX)vivado -nolog -nojournal -mode batch -source $< -tclargs $(FPGA) $(TOP) $(TOP_REP)\ + , " Synthese",$@.log) + +synthese: $(TOP)_utilization.rpt $(TOP)_timing.rpt $(TOP)_summary.rpt ##! Réalise la synthèse grossière pour évaluer les performances + @echo "Rapport d'utilisation disponible dans $(TOP)_utilization.rpt" + @echo "Rapport de timing disponible dans $(TOP)_timing.rpt" + @python3 $(COMMON_DIR)/parse_report.py $^ + +golden.bit: $(COMMON_DIR)/bitstream.vivado.tcl $(COMMON_DIR)/$(TOP).xdc RTL + @$(call check_vars,XILINX_VIVADO PROG) + $(call q, \ + $(XILINX_PREFIX)vivado -nolog -nojournal -mode batch -source $< -tclargs $(FPGA) $(TOP) $(TOP_REP) $@ $(MEM_FILE) \ + , " Génération du bitstream",$@.log) + +.prog_$(PROG_HASH): + @rm -f .prog_* # Supprimer les anciens marqueurs + @touch $@ + +download.bit: golden.bit $(MEM_FILE) $(MMI_FILE) .prog_$(PROG_HASH) + @$(call check_vars,XILINX_VIVADO PROG) + $(call q, $(XILINX_PREFIX)updatemem -debug -force --meminfo $(MMI_FILE) --data $(MEM_FILE) --proc $(MEM_ID) --bit $< --out $@\ + , " Mise à jour du fichier du bitstream", $@.mem.log) + +check_usb: + @lsusb | grep -q FT2232C || \ + ( echo "❌ Carte Digilent non détectée (FT2232C manquante)" && \ + echo " → Vérifiez le câble USB, la carte ou changez de port." && \ + exit 1 ) +check_djtgcfg: + @command -v djtgcfg >/dev/null 2>&1 || \ + ( echo "❌ Outil djtgcfg introuvable." && \ + echo " → Changez de machine ou rebootez !" && \ + exit 1 ) +check_driver: check_djtgcfg + @djtgcfg enum | grep -q "Digilent Zybo" || \ + ( echo "❌ Aucun périphérique détecté par djtgcfg." && \ + echo " → Essayez un autre port USB ou vérifiez le câble." && \ + exit 1 ) + +check_hw: check_usb check_driver + +fpga: download.bit $(COMMON_DIR)/programFPGA.vivado.tcl check_hw ##! Génére le fichier de configuration du FPGA et le programme + @$(call check_vars,XILINX_VIVADO) + $(call q, \ + $(XILINX_PREFIX)vivado -nolog -nojournal -mode batch -source $(COMMON_DIR)/programFPGA.vivado.tcl -nolog -nojournal \ + -tclargs $< \ + , " Programmation",prog.log ) + +##@ Nettoyage +clean: ##! Fais le nettoyage + $(call q, rm -rf $(TOCLEAN),) diff --git a/bench/chenillard_minimaliste.s b/bench/chenillard_minimaliste.s new file mode 100644 index 0000000..49a57d2 --- /dev/null +++ b/bench/chenillard_minimaliste.s @@ -0,0 +1,15 @@ +.text +ori x1, x0, 21 +ori x2, x0, 1 +addi x3, x3, 1 #Itérateur. Ne fonctionne que sur carte car x3 ne sera pas initialisé à 0 en simu. +srl x4, x3, x1 # Compteur tous les 2 million d'itérations environ (fréquence à l'Hertz) +and x4, x4, x2 +xor x5, x4, x6 # détection de front (x6 étant la valeur précédente de x4) +or x6, x0, x4 # Sauvegarde de la valeur précédente +# Le motif à afficher est dans x7. +# On le décale de x5 (0 ou 1) position vers la gauche +sll x7, x7, x5 +ori x7, x7, 1 +or x31, x0, x7 +# Via l'instruction de garde que rajoute l'assembleur, on réinitialise PC. +# Donc on reboucle sur la première instruction. diff --git a/bench/chenillard_rotation.s b/bench/chenillard_rotation.s new file mode 100644 index 0000000..d012d63 --- /dev/null +++ b/bench/chenillard_rotation.s @@ -0,0 +1,23 @@ +.text +# Le motif du chenillard (2 bits consécutifs) est mis dans x1 +# Le chenillard est sur 4 LED + ori x1, x0, 0x3 + +boucle_infinie: + # Durée d'attente dépend de la fréquence + li x2, 10000000 + +boucle_attente: + addi x2, x2, -1 + bne x2, x0, boucle_attente + # On affiche le motif + ori x31, x1, 0 + # On décale le motif + sll x1, x1, 1 + # On cherche un dépassement en dehors des 4 LED + andi x2, x1, 0x10 + beq x2, x0, boucle_infinie + # En cas de changement, on ramène la LED qui sort sur la première LED + andi x1, x1, 0xf + ori x1, x1, 0x1 + j boucle_infinie diff --git a/bench/compteur.s b/bench/compteur.s new file mode 100644 index 0000000..eeaddb4 --- /dev/null +++ b/bench/compteur.s @@ -0,0 +1,4 @@ +# expected: 00000001,00000002,00000003,00000004,00000005,00000006,00000007,00000008 +.text +ori x1, x0, 1 +add x31, x31, x1 diff --git a/bench/crt.S b/bench/crt.S new file mode 100644 index 0000000..567517d --- /dev/null +++ b/bench/crt.S @@ -0,0 +1,16 @@ + .section .text,"ax",@progbits + .equ STACK_SIZE, 1024 + .globl _start + +_start: + # réservation d'une (petite) zone de mémoire pour la pile + la sp, stack + STACK_SIZE + # appel de main, sans espoir de retour ! + jal main +1: j 1b + + .bss + .align 4 + .global stack +stack: + .skip STACK_SIZE diff --git a/bench/fmnsoc.h b/bench/fmnsoc.h new file mode 100644 index 0000000..d789315 --- /dev/null +++ b/bench/fmnsoc.h @@ -0,0 +1,38 @@ +#ifndef __FMNSOC_H__ +#define __FMNSOC_H__ + +#include + +// Leds et boutons +// Même adresse, leds en écriture, boutons en lecture +#define LED_LEDS 0x30000000 +#define LED_PUSHBUTTONS 0x30000000 + +// Vga sans configuration possible +#define FRAMEBUFFER 0x80000000 + +// CLint pour le timer +#define CLINT_MSIP 0x02000000 +#define CLINT_TIMER_CMP 0x02004000 +#define CLINT_TIMER_CMP_HI 0x02004004 +#define CLINT_TIMER_CMP_LO 0x02004000 +#define CLINT_TIMER 0x0200bff8 +#define CLINT_TIMER_HI 0x0200bffc +#define CLINT_TIMER_LOW 0x0200bff8 + +// Vga 320x200, 8 bits par pixels +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SIZE (DISPLAY_WIDTH * DISPLAY_HEIGHT) +#define DISPLAY_SCALE 1 + +#define TIMER_FREQ 10000000 // 10MHz +#define TIMER_RATIO 200 + +void timer_set(uint32_t period, uint32_t start_value); +void timer_wait(void); +void timer_set_and_wait(uint32_t period, uint32_t time); +void led_set(uint32_t value); +uint32_t push_button_get(); + +#endif // __FMNSOC_H__ diff --git a/bench/invaders.c b/bench/invaders.c new file mode 100644 index 0000000..3e59fb5 --- /dev/null +++ b/bench/invaders.c @@ -0,0 +1,438 @@ +#include "fmnsoc.h" +#include "invaders.h" +#define N_OBJECTS 7 + +/* + * Definition of bitmap for each line in the 8x8 pattern + * + * Since we have a word addressable bitmap, we need to reorder the patterns + * so that they display correctly. + * We do that in the definitions to avoid run time penalties. + */ +#define R(x) (((x & 0xf0) >> 4) | ((x & 0x0f) << 4)) +static uint8_t sprite_sship[8] = { + R(0b00000000), + R(0b00111100), + R(0b01111110), + R(0b11111111), + R(0b11111111), + R(0b11100111), + R(0b11000011), + R(0b11000011), +}; +static uint8_t sprite_laser[8] = { + R(0b00011000), + R(0b00011000), + R(0b00011000), + R(0b00011000), + R(0b00011000), + R(0b00011000), + R(0b00011000), + R(0b00011000), +}; +static uint8_t sprite_alien1[8] = { + R(0b11000011), + R(0b00111100), + R(0b01011010), + R(0b11111111), + R(0b11111111), + R(0b10000001), + R(0b01000010), + R(0b00100100), +}; +static uint8_t sprite_alien2[8] = { + R(0b11000011), + R(0b00111100), + R(0b01011010), + R(0b11111111), + R(0b11111111), + R(0b10100101), + R(0b10100101), + R(0b01011010), +}; +static uint8_t sprite_alien3[8] = { + R(0b01000010), + R(0b00100100), + R(0b00111100), + R(0b01011010), + R(0b11111111), + R(0b10111101), + R(0b10000001), + R(0b01000010), +}; +static uint8_t sprite_alien4[8] = { + R(0b11000011), + R(0b00100100), + R(0b10111101), + R(0b11100111), + R(0b00111100), + R(0b00100100), + R(0b01011010), + R(0b10011001), +}; +static uint8_t sprite_alien5[8] = { + R(0b01100110), + R(0b01111110), + R(0b11011011), + R(0b01111110), + R(0b10111101), + R(0b10100101), + R(0b10000001), + R(0b01100110), +}; +// bleu, vert, rouge pour tenir dans l'uint8_t de color_t comme il faut +static const color_t black = {0, 0, 0}; +static const color_t white = {7, 7, 3}; +static const color_t red = {0, 0, 3}; +static const color_t green = {0, 7, 0}; +static const color_t blue = {7, 0, 0}; +static const color_t magenta = {7, 0, 3}; +static const color_t cyan = {7, 7, 1}; +static const color_t yellow = {0, 7, 3}; + +/* sprite objects */ +object_t object[N_OBJECTS] = { + /* blue spaceship */ + {1, 3, 1, 18, 23, 0, 0, sprite_sship, blue, {[0 ... 7]={0}}, 0, 0}, + /* white laser */ + {0, 1, 1, 18, 0, 0, 0, sprite_laser, white, {[0 ... 7]={0}}, 0, 0}, + /* green alien */ + {1, 4, 1, 10, 0, -1, 0, sprite_alien1, green, {[0 ... 7]={0}}, 0, 0}, + /* red alien */ + {1, 4, 1, 18, 0, -1, 0, sprite_alien2, red, {[0 ... 7]={0}}, 0, 0}, + /* magenta alien */ + {1, 4, 1, 26, 0, -1, 0, sprite_alien3, magenta, {[0 ... 7]={0}}, 0, 0}, + /* yellow alien */ + {1, 4, 1, 14, 2, -1, 0, sprite_alien4, yellow, {[0 ... 7]={0}}, 0, 0}, + /* cyan alien */ + {1, 4, 1, 22, 2, -1, 0, sprite_alien5, cyan, {[0 ... 7]={0}}, 0, 0} +}; + +/* Tableau d'états pour la mécanique du jeu */ +state_t state[5] = { + {0, 1, 1}, + {0, 1, 2}, + {1, 0, 3}, + {0, 1, 4}, + {-1, 0, 1} +}; + +/* + * Deux pointeurs vers le frame buffer pour éviter les problèmes + * d'aliasing sur les types. + */ +static volatile color_t *img = (volatile color_t *)FRAMEBUFFER; +static volatile uint32_t *framebuffer = (volatile uint32_t *)FRAMEBUFFER; +static volatile uint32_t *led = (volatile uint32_t *)LED_LEDS; +static volatile uint32_t *push = (volatile uint32_t *)LED_PUSHBUTTONS; +static volatile uint64_t *timer = (volatile uint64_t *)CLINT_TIMER; +static volatile uint64_t *timer_cmp = (volatile uint64_t *)CLINT_TIMER_CMP; +static volatile uint32_t *timer_hi = (volatile uint32_t *)CLINT_TIMER_HI; +static volatile uint32_t *timer_lo = (volatile uint32_t *)CLINT_TIMER_LOW; +static volatile uint32_t *timer_cmp_hi = (volatile uint32_t *)CLINT_TIMER_CMP_HI; +static volatile uint32_t *timer_cmp_lo = (volatile uint32_t *)CLINT_TIMER_CMP_LO; + +uint32_t mult(uint64_t x, uint64_t y) +{ + uint64_t res = 0; + while (y != 0) { + if (y % 2 == 1) { + res += x; + } + x <<= 1; + y >>= 1; + } + return res; +} + +void timer_set(uint32_t period, uint32_t time) +{ + uint64_t now = *timer; + *timer_cmp = now + mult(((uint64_t)period >> 8), time); +} + +void timer_wait(void) +{ + while (*timer <= *timer_cmp); +} + +void timer_set_and_wait(uint32_t period, uint32_t time) +{ + timer_set(period, time); + timer_wait(); +} + +void led_set(uint32_t value) +{ + *led = value; +} + +uint32_t push_button_get() +{ + return *push; +} + + +/* + * main program + * --------------------------------------------------------------------------- + */ +int main(void) +{ + /* declaration of local variables */ + uint32_t i; + uint32_t push_state, led_state, alien_state, edge_reached; + uint32_t n_aliens; + object_t *spaceship, *laser; + + init: + /* initialization stage */ + push_state = 0; /* no button pressed at beginning */ + led_state = 0; /* initial value displayed on leds */ + alien_state = 0; /* state of alien in a line */ + edge_reached = 0; /* no edge reached at beginning */ + n_aliens = N_OBJECTS - 2; /* number of displayed aliens */ + initialize(); + laser = &object[1]; /* laser is the second declared object */ + spaceship = &object[0]; /* spaceship is the first declared object */ + spaceship->x = 20; /* set spaceship at the middle */ + spaceship->y = 25; /* and bottom of the screen */ +#define MAX_X 39 + + /* display stage */ + while (1) { + edge_reached = 0; + + /* decrease deadline of alive objects */ + for (i = 0; i < N_OBJECTS; i++) { + if (object[i].alive) + object[i].deadline--; + } + + /* display all alive objects */ + for (i = 0; i < N_OBJECTS; i++) { + if (object[i].alive) + display_sprite(&object[i]); + } + + /* determine new positions of all alive objects */ + for (i = 0; i < N_OBJECTS; i++) { + /* update object state when deadline is reached */ + if (object[i].alive && object[i].deadline == 0) { + /* reinitialize the object deadline to period */ + object[i].deadline = object[i].period; + /* determine new position and manage screen edges */ + object[i].x += object[i].dx; + if (object[i].x < 0) + object[i].x = 0; + if (object[i].x > MAX_X) + object[i].x = MAX_X; + object[i].y += object[i].dy; + /* test if an edge of the screen was reached by an alien */ + if (i >= 2 && (object[i].x == 0 || object[i].x == MAX_X)) + edge_reached = 1; + /* store background of the next position */ + if (i > 1 && object[i].y >= spaceship->y) { + clear_screen(blue); /* blue screen */ + timer_set_and_wait(TIMER_FREQ, 1000); + initialize(); + } + } + } + + /* test if alien is hit by an alive laser */ + if (laser->alive) { + for (i = 2; i < N_OBJECTS; i++) { + if (object[i].alive && laser->x == object[i].x && laser->y == object[i].y) { + n_aliens--; + object[i].alive = 0; + laser->alive = 0; + if (n_aliens == 0) { + /* no more aliens */ + spaceship->alive = 0; + clear_screen(yellow); /* yellow screen */ + timer_set_and_wait(TIMER_FREQ, 1000); + clear_screen(red); /* red screen */ + } else { + display_sprite(&object[i]); + display_sprite(laser); + } + } + } + } + + /* when an alien reaches a screen edge, the group of aliens is moved */ + if (edge_reached) { + for (i = 2; i < N_OBJECTS; i++) { + object[i].dx = state[alien_state].dx; + object[i].dy = state[alien_state].dy; + } + alien_state = state[alien_state].next_state; + } + + /* laser disappears when it reaches the screen top */ + if (laser->alive && laser->y == 0) { + laser->alive = 0; + display_sprite(laser); + } + + /* manage push buttons */ + push_state = push_button_get(); + // if we won, press fire to restart + if ((n_aliens == 0) + && (push_state & 0x4)) { + goto init; + } + if ((spaceship->deadline == 1) + || (n_aliens == 0)) { + spaceship->dx = 0; + if (push_state & 0x1) + /* to the right */ + spaceship->dx = 1; + if (push_state & 0x2) + /* to the left */ + spaceship->dx = -1; + if (push_state & 0x4) { + /* fire a laser */ + if (!laser->alive) { + laser->alive = 1; + laser->dx = 0; + laser->dy = -1; + laser->x = spaceship->x; + laser->y = spaceship->y - 1; + laser->deadline = laser->period; + } + } + } + + /* manage leds' state */ + led_set(led_state); + led_state++; + timer_set_and_wait(TIMER_FREQ, 4); + } +} + +/* + * definition of functions + * --------------------------------------------------------------------------- + */ + +/* function to read a pixel from a (x,y) position of video framebuffer */ +color_t read_pixel(uint32_t x, uint32_t y) +{ + uint32_t real_y = y * DISPLAY_WIDTH * DISPLAY_SCALE; + uint32_t real_x = x * DISPLAY_SCALE; + return img[real_y + real_x]; +} + +void write_pixel_scaling(color_t pixel, uint32_t x, uint32_t y) +{ + for (int i = 0; i < DISPLAY_SCALE; ++i) { + for (int j = 0; j < DISPLAY_SCALE; ++j) { + uint32_t real_y = y * DISPLAY_SCALE + i; + uint32_t real_x = x * DISPLAY_SCALE + j; + + img[real_y * DISPLAY_WIDTH + real_x] = pixel; + } + } +} + +void memsetw(volatile uint32_t* dest, uint32_t c, uint32_t n) +{ + volatile uint32_t *p = dest; + while (n-- > 0) { + *(volatile uint32_t *)dest++ = c; + } +} + +void clear_screen(color_t color) +{ + pixels_t p; + p.pixel[0] = color; + p.pixel[1] = color; + p.pixel[2] = color; + p.pixel[3] = color; + memsetw(framebuffer, p.pixels, DISPLAY_SIZE / 4); +} + +/* function to initialize all objects */ +void initialize() +{ + uint32_t i, dx, dy; + clear_screen(black); /* black screen */ + for (i = 0; i < N_OBJECTS; i++) { + if (i == 1) { + /* laser */ + object[i].alive = 0; + object[i].period = 1; + } else { + /* spaceship or aliens */ + object[i].alive = 1; + if (i == 0) + /* spaceship */ + object[i].period = 3; + else + /* aliens */ + object[i].period = 4; + } + object[i].deadline = 1; + if (i > 1) { + /* aliens */ + if (i > 4) { + /* alien4 or alien5 */ + object[i].y = 3; /* 3rd line */ + object[i].x = 6 + (i - 4) * 8; + } else { + /* alien1, alien2 or alien3 */ + object[i].y = 1; /* 1st line */ + object[i].x = 10 + (i - 2) * 8; + } + object[i].dx = -1; + object[i].dy = 0; + } + object[i].ax = -1; + object[i].ay = -1; + + /* initialization of object background considering the last one */ + for (dx = 0; dx < 8; dx++) + for (dy = 0; dy < 8; dy++) + object[i].bg[dx][dy] = black; + } +} + +/* function to display the 8 pixels of a pattern line */ +void display_pattern_line(uint8_t m, uint32_t x, uint32_t y, color_t color) +{ + for (int i = 0; i < 8; i++) { + color_t new_color = (m & 1) == 1 ? color : black; + m >>= 1; + write_pixel_scaling(new_color, x + i, y); + } +} + +/* function to display an 8x8 object considering the last background */ +void display_pattern(uint8_t pattern[8], uint32_t x, uint32_t y, color_t color) +{ + for (int i = 0; i < 8; i++) + display_pattern_line(pattern[i], x, y + i, color); +} + +/* function to display an 8x8 object (spaceship, laser or alien) */ +void display_sprite(object_t *object) +{ + if ((object->ax > -1 && object->ay > -1) && (object->x != object->ax || object->y != object->ay || !object->alive)) { + for (uint32_t dx = 0; dx < 8; dx++) { + for (uint32_t dy = 0; dy < 8; dy++) { + write_pixel_scaling(object->bg[dx][dy], ((object->ax) << 3) + dx, ((object->ay) << 3) + dy); + if (!object->alive) + object->bg[dx][dy] = black; + } + } + } + + object->ax = object->x; + object->ay = object->y; + + if (object->alive) + display_pattern(object->pattern, (object->x) << 3, (object->y) << 3, object->color); +} diff --git a/bench/invaders.h b/bench/invaders.h new file mode 100644 index 0000000..28bb33f --- /dev/null +++ b/bench/invaders.h @@ -0,0 +1,49 @@ +#ifndef __SPRITE_H__ +#define __SPRITE_H__ +#include + +#define OBJECT_SIZE 8 + +struct color_t { + uint8_t b:3; + uint8_t g:3; + uint8_t r:2; +} __attribute__((packed)); +typedef struct color_t color_t; + +typedef union { + color_t pixel[4]; + uint32_t pixels; +} pixels_t; + + +/* Objet pour représenter les aliens, le vaisseau et le laser */ +typedef struct object_t { + uint32_t alive; + uint32_t period; + uint32_t deadline; + int x, y; + int dx, dy; + uint8_t *pattern; + color_t color; + color_t bg[OBJECT_SIZE][OBJECT_SIZE]; + int ax, ay; +} object_t; + +/* État pour implanter la mécanique du jeu sous forme de machine à états */ +typedef struct state_t { + int dx; + int dy; + int next_state; +} state_t; + +/* Prototype des fonctions définies dans invaders.c */ +color_t read_pixel(uint32_t x, uint32_t y); +void write_pixel_scaling(color_t pixel, uint32_t x, uint32_t y); +void clear_screen(color_t color); +void initialize(void); +void display_pattern_line(uint8_t m, uint32_t x, uint32_t y, color_t color); +void display_pattern(uint8_t pattern[OBJECT_SIZE], uint32_t x, uint32_t y, color_t color); +void display_sprite(object_t *object); +void display_timer(void); +#endif diff --git a/bench/link.ld b/bench/link.ld new file mode 100644 index 0000000..ff98fb3 --- /dev/null +++ b/bench/link.ld @@ -0,0 +1,47 @@ +OUTPUT_ARCH( "riscv" ) + +ENTRY (_start) +MEMORY +{ + insnmem (rx) : ORIGIN = 0x0000, LENGTH = 8K + datamem (wai!x) : ORIGIN = 0x2000, LENGTH = 8K +} + +PHDRS +{ + text PT_LOAD; + data PT_LOAD; + bss PT_LOAD; +} + +SECTIONS +{ + PROVIDE(_start = ORIGIN(insnmem)); + .text : { + PROVIDE(_text_start = .); + *(.text._start) *(.text .text.*) + PROVIDE(_text_end = .); + } >insnmem AT>insnmem :text + + . = ORIGIN(datamem); + .rodata ALIGN(4) : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >datamem AT>datamem :data + + .data ALIGN(4) : { + PROVIDE(_data_start = .); + *(.sdata .sdata.*) *(.data .data.*) + PROVIDE(_data_end = .); + } >datamem AT>datamem :data + + .bss ALIGN(4) : { + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) *(.bss .bss.*) + PROVIDE(_bss_end = .); + } >datamem AT>datamem :bss + + PROVIDE(_memory_start = ORIGIN(datamem)); + PROVIDE(_memory_end = ORIGIN(datamem) + LENGTH(datamem)); +} diff --git a/bench/lui.s b/bench/lui.s new file mode 100644 index 0000000..80caedc --- /dev/null +++ b/bench/lui.s @@ -0,0 +1,7 @@ +# expected: 00000000,FFFFF000,12345000 + .text + + lui x31, 0 #Test chargement d'une valeur nulle + lui x31, 0xfffff #Test chargement d'une valeur maximal sur 20 bits + lui x31, 0x12345 #Test chargement d'une valeur quelconque + diff --git a/build.sc b/build.sc new file mode 100644 index 0000000..84d50fe --- /dev/null +++ b/build.sc @@ -0,0 +1,41 @@ +// import Mill dependency +import mill._ +import mill.define.Sources +import mill.modules.Util +import mill.scalalib.TestModule.ScalaTest +import scalalib._ +// support BSP +import mill.bsp._ + +// Note: This project requires .mill-jvm-opts file containing: +// -Dchisel.project.root=${PWD} +// This is needed because Chisel needs to know the project root directory +// to properly generate and handle test directories and output files. +// See: https://github.com/com-lihaoyi/mill/issues/3840 + +object `TPchisel` extends SbtModule { m => + override def millSourcePath = super.millSourcePath / os.up + override def scalaVersion = "2.13.16" + override def scalacOptions = Seq( + "-language:reflectiveCalls", + "-deprecation", + "-feature", + "-Xcheckinit", + "-Ymacro-annotations", + "-unchecked", + "-Xfatal-warnings", + "-Ywarn-dead-code", + "-Ywarn-unused" + ) + override def ivyDeps = Agg( + ivy"org.chipsalliance::chisel:7.2.0", + ) + override def scalacPluginIvyDeps = Agg( + ivy"org.chipsalliance:::chisel-plugin:7.2.0", + ) + object test extends SbtTests with TestModule.ScalaTest { + override def ivyDeps = m.ivyDeps() ++ Agg( + ivy"org.scalatest::scalatest::3.2.19" + ) + } +} diff --git a/common/ZzTop.xdc b/common/ZzTop.xdc new file mode 100644 index 0000000..688288c --- /dev/null +++ b/common/ZzTop.xdc @@ -0,0 +1,147 @@ +## This file is a general .xdc for the ZYBO Rev B board +## To use it in a project: +## - uncomment the lines corresponding to used pins +## - rename the used signals according to the project + + +# Clock signal +set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { clock }]; #IO_L11P_T1_SRCC_35 Sch=sysclk + +create_clock -add -name sys_clk_pin -period 8.00 -waveform {0 4} [get_ports { clock }]; + +# Switches +set_property -dict { PACKAGE_PIN G15 IOSTANDARD LVCMOS33 } [get_ports { io_switch[0] }]; #IO_L19N_T3_VREF_35 Sch=SW0 +set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS33 } [get_ports { io_switch[1] }]; #IO_L24P_T3_34 Sch=SW1 +set_property -dict { PACKAGE_PIN W13 IOSTANDARD LVCMOS33 } [get_ports { io_switch[2] }]; #IO_L4N_T0_34 Sch=SW2 +set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { io_switch[3] }]; #IO_L9P_T1_DQS_34 Sch=SW3 + + +# Buttons +set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { io_push[0] }]; #IO_L20N_T3_34 Sch=BTN0 +set_property -dict { PACKAGE_PIN P16 IOSTANDARD LVCMOS33 } [get_ports { io_push[1] }]; #IO_L24N_T3_34 Sch=BTN1 +set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { io_push[2] }]; #IO_L18P_T2_34 Sch=BTN2 +set_property -dict { PACKAGE_PIN Y16 IOSTANDARD LVCMOS33 } [get_ports { reset }]; #IO_L7P_T1_34 Sch=BTN3 + + +# LEDs +set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [get_ports { io_led[0] }]; #IO_L23P_T3_35 Sch=LED0 +set_property -dict { PACKAGE_PIN M15 IOSTANDARD LVCMOS33 } [get_ports { io_led[1] }]; #IO_L23N_T3_35 Sch=LED1 +set_property -dict { PACKAGE_PIN G14 IOSTANDARD LVCMOS33 } [get_ports { io_led[2] }]; #IO_0_35 Sch=LED2 +set_property -dict { PACKAGE_PIN D18 IOSTANDARD LVCMOS33 } [get_ports { io_led[3] }]; #IO_L3N_T0_DQS_AD1N_35 Sch=LED3 + + +##I2S Audio Codec +#set_property -dict { PACKAGE_PIN K18 IOSTANDARD LVCMOS33 } [get_ports { ac_bclk }]; #IO_L12N_T1_MRCC_35 Sch=AC_BCLK +#set_property -dict { PACKAGE_PIN T19 IOSTANDARD LVCMOS33 } [get_ports { ac_mclk }]; #IO_25_34 Sch=AC_MCLK +#set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { ac_muten }]; #IO_L23N_T3_34 Sch=AC_MUTEN +#set_property -dict { PACKAGE_PIN M17 IOSTANDARD LVCMOS33 } [get_ports { ac_pbdat }]; #IO_L8P_T1_AD10P_35 Sch=AC_PBDAT +#set_property -dict { PACKAGE_PIN L17 IOSTANDARD LVCMOS33 } [get_ports { ac_pblrc }]; #IO_L11N_T1_SRCC_35 Sch=AC_PBLRC +#set_property -dict { PACKAGE_PIN K17 IOSTANDARD LVCMOS33 } [get_ports { ac_recdat }]; #IO_L12P_T1_MRCC_35 Sch=AC_RECDAT +#set_property -dict { PACKAGE_PIN M18 IOSTANDARD LVCMOS33 } [get_ports { ac_reclrc }]; #IO_L8N_T1_AD10N_35 Sch=AC_RECLRC + + +##Audio Codec/external EEPROM IIC bus +#set_property -dict { PACKAGE_PIN N18 IOSTANDARD LVCMOS33 } [get_ports { ac_scl }]; #IO_L13P_T2_MRCC_34 Sch=AC_SCL +#set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { ac_sda }]; #IO_L23P_T3_34 Sch=AC_SDA + + +##Additional Ethernet signals +#set_property -dict { PACKAGE_PIN F16 IOSTANDARD LVCMOS33 } [get_ports { eth_int_b }]; #IO_L6P_T0_35 Sch=ETH_INT_B +#set_property -dict { PACKAGE_PIN E17 IOSTANDARD LVCMOS33 } [get_ports { eth_rst_b }]; #IO_L3P_T0_DQS_AD1P_35 Sch=ETH_RST_B + + +##HDMI Signals +#set_property -dict { PACKAGE_PIN H17 IOSTANDARD TMDS_33 } [get_ports { hdmi_clk_n }]; #IO_L13N_T2_MRCC_35 Sch=HDMI_CLK_N +#set_property -dict { PACKAGE_PIN H16 IOSTANDARD TMDS_33 } [get_ports { hdmi_clk_p }]; #IO_L13P_T2_MRCC_35 Sch=HDMI_CLK_P +#set_property -dict { PACKAGE_PIN D20 IOSTANDARD TMDS_33 } [get_ports { hdmi_d_n[0] }]; #IO_L4N_T0_35 Sch=HDMI_D0_N +#set_property -dict { PACKAGE_PIN D19 IOSTANDARD TMDS_33 } [get_ports { hdmi_d_p[0] }]; #IO_L4P_T0_35 Sch=HDMI_D0_P +#set_property -dict { PACKAGE_PIN B20 IOSTANDARD TMDS_33 } [get_ports { hdmi_d_n[1] }]; #IO_L1N_T0_AD0N_35 Sch=HDMI_D1_N +#set_property -dict { PACKAGE_PIN C20 IOSTANDARD TMDS_33 } [get_ports { hdmi_d_p[1] }]; #IO_L1P_T0_AD0P_35 Sch=HDMI_D1_P +#set_property -dict { PACKAGE_PIN A20 IOSTANDARD TMDS_33 } [get_ports { hdmi_d_n[2] }]; #IO_L2N_T0_AD8N_35 Sch=HDMI_D2_N +#set_property -dict { PACKAGE_PIN B19 IOSTANDARD TMDS_33 } [get_ports { hdmi_d_p[2] }]; #IO_L2P_T0_AD8P_35 Sch=HDMI_D2_P +#set_property -dict { PACKAGE_PIN E19 IOSTANDARD LVCMOS33 } [get_ports { hdmi_cec }]; #IO_L5N_T0_AD9N_35 Sch=HDMI_CEC +#set_property -dict { PACKAGE_PIN E18 IOSTANDARD LVCMOS33 } [get_ports { hdmi_hpd }]; #IO_L5P_T0_AD9P_35 Sch=HDMI_HPD +#set_property -dict { PACKAGE_PIN F17 IOSTANDARD LVCMOS33 } [get_ports { hdmi_out_en }]; #IO_L6N_T0_VREF_35 Sch=HDMI_OUT_EN +#set_property -dict { PACKAGE_PIN G17 IOSTANDARD LVCMOS33 } [get_ports { hdmi_scl }]; #IO_L16P_T2_35 Sch=HDMI_SCL +#set_property -dict { PACKAGE_PIN G18 IOSTANDARD LVCMOS33 } [get_ports { hdmi_sda }]; #IO_L16N_T2_35 Sch=HDMI_SDA + + +##Pmod Header JA (XADC) +#set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 } [get_ports { ja_p[0] }]; #IO_L21P_T3_DQS_AD14P_35 Sch=JA1_R_p +#set_property -dict { PACKAGE_PIN L14 IOSTANDARD LVCMOS33 } [get_ports { ja_p[1] }]; #IO_L22P_T3_AD7P_35 Sch=JA2_R_P +#set_property -dict { PACKAGE_PIN K16 IOSTANDARD LVCMOS33 } [get_ports { ja_p[2] }]; #IO_L24P_T3_AD15P_35 Sch=JA3_R_P +#set_property -dict { PACKAGE_PIN K14 IOSTANDARD LVCMOS33 } [get_ports { ja_p[3] }]; #IO_L20P_T3_AD6P_35 Sch=JA4_R_P +#set_property -dict { PACKAGE_PIN N16 IOSTANDARD LVCMOS33 } [get_ports { ja_n[0] }]; #IO_L21N_T3_DQS_AD14N_35 Sch=JA1_R_N +#set_property -dict { PACKAGE_PIN L15 IOSTANDARD LVCMOS33 } [get_ports { ja_n[1] }]; #IO_L22N_T3_AD7N_35 Sch=JA2_R_N +#set_property -dict { PACKAGE_PIN J16 IOSTANDARD LVCMOS33 } [get_ports { ja_n[2] }]; #IO_L24N_T3_AD15N_35 Sch=JA3_R_N +#set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { ja_n[3] }]; #IO_L20N_T3_AD6N_35 Sch=JA4_R_N + + +##Pmod Header JB +#set_property -dict { PACKAGE_PIN T20 IOSTANDARD LVCMOS33 } [get_ports { jb_p[0] }]; #IO_L15P_T2_DQS_34 Sch=JB1_p +#set_property -dict { PACKAGE_PIN U20 IOSTANDARD LVCMOS33 } [get_ports { jb_n[0] }]; #IO_L15N_T2_DQS_34 Sch=JB1_N +#set_property -dict { PACKAGE_PIN V20 IOSTANDARD LVCMOS33 } [get_ports { jb_p[1] }]; #IO_L16P_T2_34 Sch=JB2_P +#set_property -dict { PACKAGE_PIN W20 IOSTANDARD LVCMOS33 } [get_ports { jb_n[1] }]; #IO_L16N_T2_34 Sch=JB2_N +#set_property -dict { PACKAGE_PIN Y18 IOSTANDARD LVCMOS33 } [get_ports { jb_p[2] }]; #IO_L17P_T2_34 Sch=JB3_P +#set_property -dict { PACKAGE_PIN Y19 IOSTANDARD LVCMOS33 } [get_ports { jb_n[2] }]; #IO_L17N_T2_34 Sch=JB3_N +#set_property -dict { PACKAGE_PIN W18 IOSTANDARD LVCMOS33 } [get_ports { jb_p[3] }]; #IO_L22P_T3_34 Sch=JB4_P +#set_property -dict { PACKAGE_PIN W19 IOSTANDARD LVCMOS33 } [get_ports { jb_n[3] }]; #IO_L22N_T3_34 Sch=JB4_N + + +##Pmod Header JC +#set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { jc_p[0] }]; #IO_L10P_T1_34 Sch=JC1_P +#set_property -dict { PACKAGE_PIN W15 IOSTANDARD LVCMOS33 } [get_ports { jc_n[0] }]; #IO_L10N_T1_34 Sch=JC1_N +#set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { jc_p[1] }]; #IO_L1P_T0_34 Sch=JC2_P +#set_property -dict { PACKAGE_PIN T10 IOSTANDARD LVCMOS33 } [get_ports { jc_n[1] }]; #IO_L1N_T0_34 Sch=JC2_N +#set_property -dict { PACKAGE_PIN W14 IOSTANDARD LVCMOS33 } [get_ports { jc_p[2] }]; #IO_L8P_T1_34 Sch=JC3_P +#set_property -dict { PACKAGE_PIN Y14 IOSTANDARD LVCMOS33 } [get_ports { jc_n[2] }]; #IO_L8N_T1_34 Sch=JC3_N +#set_property -dict { PACKAGE_PIN T12 IOSTANDARD LVCMOS33 } [get_ports { jc_p[3] }]; #IO_L2P_T0_34 Sch=JC4_P +#set_property -dict { PACKAGE_PIN U12 IOSTANDARD LVCMOS33 } [get_ports { jc_n[3] }]; #IO_L2N_T0_34 Sch=JC4_N + + +##Pmod Header JD +#set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { jd_p[0] }]; #IO_L5P_T0_34 Sch=JD1_P +#set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { jd_n[0] }]; #IO_L5N_T0_34 Sch=JD1_N +#set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { jd_p[1] }]; #IO_L6P_T0_34 Sch=JD2_P +#set_property -dict { PACKAGE_PIN R14 IOSTANDARD LVCMOS33 } [get_ports { jd_n[1] }]; #IO_L6N_T0_VREF_34 Sch=JD2_N +#set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { jd_p[2] }]; #IO_L11P_T1_SRCC_34 Sch=JD3_P +#set_property -dict { PACKAGE_PIN U15 IOSTANDARD LVCMOS33 } [get_ports { jd_n[2] }]; #IO_L11N_T1_SRCC_34 Sch=JD3_N +#set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { jd_p[3] }]; #IO_L21P_T3_DQS_34 Sch=JD4_P +#set_property -dict { PACKAGE_PIN V18 IOSTANDARD LVCMOS33 } [get_ports { jd_n[3] }]; #IO_L21N_T3_DQS_34 Sch=JD4_N + + +##Pmod Header JE +#set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { je[0] }]; #IO_L4P_T0_34 Sch=JE1 +#set_property -dict { PACKAGE_PIN W16 IOSTANDARD LVCMOS33 } [get_ports { je[1] }]; #IO_L18N_T2_34 Sch=JE2 +#set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { je[2] }]; #IO_25_35 Sch=JE3 +#set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { je[3] }]; #IO_L19P_T3_35 Sch=JE4 +#set_property -dict { PACKAGE_PIN V13 IOSTANDARD LVCMOS33 } [get_ports { je[4] }]; #IO_L3N_T0_DQS_34 Sch=JE7 +#set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { je[5] }]; #IO_L9N_T1_DQS_34 Sch=JE8 +#set_property -dict { PACKAGE_PIN T17 IOSTANDARD LVCMOS33 } [get_ports { je[6] }]; #IO_L20P_T3_34 Sch=JE9 +#set_property -dict { PACKAGE_PIN Y17 IOSTANDARD LVCMOS33 } [get_ports { je[7] }]; #IO_L7N_T1_34 Sch=JE10 + + +##USB-OTG overcurrent detect pin +#set_property -dict { PACKAGE_PIN U13 IOSTANDARD LVCMOS33 } [get_ports { otg_oc }]; #IO_L3P_T0_DQS_PUDC_B_34 Sch=OTG_OC + + +#VGA Connector +set_property -dict { PACKAGE_PIN M19 IOSTANDARD LVCMOS33 } [get_ports { io_r[0] }]; #IO_L7P_T1_AD2P_35 Sch=VGA_R1 +set_property -dict { PACKAGE_PIN L20 IOSTANDARD LVCMOS33 } [get_ports { io_r[1] }]; #IO_L9N_T1_DQS_AD3N_35 Sch=VGA_R2 +set_property -dict { PACKAGE_PIN J20 IOSTANDARD LVCMOS33 } [get_ports { io_r[2] }]; #IO_L17P_T2_AD5P_35 Sch=VGA_R3 +set_property -dict { PACKAGE_PIN G20 IOSTANDARD LVCMOS33 } [get_ports { io_r[3] }]; #IO_L18N_T2_AD13N_35 Sch=VGA_R4 +set_property -dict { PACKAGE_PIN F19 IOSTANDARD LVCMOS33 } [get_ports { io_r[4] }]; #IO_L15P_T2_DQS_AD12P_35 Sch=VGA_R5 +set_property -dict { PACKAGE_PIN H18 IOSTANDARD LVCMOS33 } [get_ports { io_g[0] }]; #IO_L14N_T2_AD4N_SRCC_35 Sch=VGA_G0 +set_property -dict { PACKAGE_PIN N20 IOSTANDARD LVCMOS33 } [get_ports { io_g[1] }]; #IO_L14P_T2_SRCC_34 Sch=VGA_G1 +set_property -dict { PACKAGE_PIN L19 IOSTANDARD LVCMOS33 } [get_ports { io_g[2] }]; #IO_L9P_T1_DQS_AD3P_35 Sch=VGA_G2 +set_property -dict { PACKAGE_PIN J19 IOSTANDARD LVCMOS33 } [get_ports { io_g[3] }]; #IO_L10N_T1_AD11N_35 Sch=VGA_G3 +set_property -dict { PACKAGE_PIN H20 IOSTANDARD LVCMOS33 } [get_ports { io_g[4] }]; #IO_L17N_T2_AD5N_35 Sch=VGA_G4 +set_property -dict { PACKAGE_PIN F20 IOSTANDARD LVCMOS33 } [get_ports { io_g[5] }]; #IO_L15N_T2_DQS_AD12N_35 Sch=VGA=G5 +set_property -dict { PACKAGE_PIN P20 IOSTANDARD LVCMOS33 } [get_ports { io_b[0] }]; #IO_L14N_T2_SRCC_34 Sch=VGA_B1 +set_property -dict { PACKAGE_PIN M20 IOSTANDARD LVCMOS33 } [get_ports { io_b[1] }]; #IO_L7N_T1_AD2N_35 Sch=VGA_B2 +set_property -dict { PACKAGE_PIN K19 IOSTANDARD LVCMOS33 } [get_ports { io_b[2] }]; #IO_L10P_T1_AD11P_35 Sch=VGA_B3 +set_property -dict { PACKAGE_PIN J18 IOSTANDARD LVCMOS33 } [get_ports { io_b[3] }]; #IO_L14P_T2_AD4P_SRCC_35 Sch=VGA_B4 +set_property -dict { PACKAGE_PIN G19 IOSTANDARD LVCMOS33 } [get_ports { io_b[4] }]; #IO_L18P_T2_AD13P_35 Sch=VGA_B5 +set_property -dict { PACKAGE_PIN P19 IOSTANDARD LVCMOS33 } [get_ports { io_hs }]; #IO_L13N_T2_MRCC_34 Sch=VGA_HS +set_property -dict { PACKAGE_PIN R19 IOSTANDARD LVCMOS33 } [get_ports { io_vs }]; #IO_0_34 Sch=VGA_VS + diff --git a/common/bitstream.vivado.tcl b/common/bitstream.vivado.tcl new file mode 100644 index 0000000..fe31b8b --- /dev/null +++ b/common/bitstream.vivado.tcl @@ -0,0 +1,61 @@ +# Input arguments +set DEVICE [lindex $argv 0] +set TOP [lindex $argv 1] +set REP [lindex $argv 2] +set TARGET [lindex $argv 3] +set MEM_FILE [file normalize [lindex $argv 4]] + +source common/reportCriticalPaths.tcl + +#lecture de tous les sv générés +set files [glob RTL/${REP}.${TOP}/*.sv] +read_verilog -sv $files +#ajout de clock à 10 MHz et 125 MHz +read_verilog -sv src/main/resources/vsrc/clk_wiz.v +read_verilog -sv src/main/resources/vsrc/dpram.v + +set_param general.maxThreads 4 +# Reading constraint file (.xdc file) +read_xdc common/${TOP}.xdc + +# Detect XPM memory +auto_detect_xpm + +# Start synthesis +synth_design -top ${TOP} -part ${DEVICE} -fanout_limit 100 +#-flatten_hierarchy none +#-directive RuntimeOptimized + +# Run logic optimization +opt_design + +# Placing +place_design +#-directive Quick +#write_checkpoint -force place_design.dcp + +# Routing +route_design -ultrathreads +# -directive Quick +#write_checkpoint -force route_design.dcp + +# Reports +report_timing -file ${TOP}_timing.rpt +report_timing_summary -max_paths 500 -nworst 1 -input_pins -file ${TOP}_timing_summary.rpt +report_utilization -file ${TOP}_utilization_opt.rpt +report_critical_paths ${TOP}_critpath_report.csv +report_route_status -file ${TOP}_route_status.rpt +report_design_analysis -file ${TOP}_design_analysis.rpt +report_io -file ${TOP}_io_opt.rpt +report_drc -file ${TOP}_drc_route.rpt +report_clock_interaction -file ${TOP}_clock_interaction_opt.rpt + +# Generate MMI map +write_mem_info -force ${TOP}.mmi + +# Create bitstream +write_bitstream -force ${TARGET} + +write_checkpoint -force bitstream_design.dcp + +exit diff --git a/common/clock.xdc b/common/clock.xdc new file mode 100644 index 0000000..d909152 --- /dev/null +++ b/common/clock.xdc @@ -0,0 +1,5 @@ +# Clock signal +create_clock -add -name sys_clk_pin -period 8.00 -waveform {0 4} [get_ports { clock }]; + +set_input_delay -clock sys_clk_pin 0.000 [all_inputs] +set_output_delay -clock sys_clk_pin 0.000 [all_outputs] diff --git a/common/config.gtkw b/common/config.gtkw new file mode 100644 index 0000000..12f5db2 --- /dev/null +++ b/common/config.gtkw @@ -0,0 +1,76 @@ +[*] +[*] GTKWave Analyzer v3.3.116 (w)1999-2023 BSI +[*] Mon Nov 3 08:12:34 2025 +[*] +[dumpfile] "/user/9/.base/mullero/home/cours/FMN/projet/build/chiselsim/ZzTopSpec/Simulation-pour-gtkwave/workdir-verilator/trace.vcd" +[dumpfile_mtime] "Mon Nov 3 08:11:19 2025" +[dumpfile_size] 59916 +[savefile] "/user/9/.base/mullero/home/cours/FMN/projet/common/config.gtkw" +[timestart] 0 +[size] 1920 1080 +[pos] -67 -33 +*-14.058537 52 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +[treeopen] TOP. +[treeopen] TOP.svsimTestbench. +[treeopen] TOP.svsimTestbench.dut. +[treeopen] TOP.svsimTestbench.dut.core. +[sst_width] 278 +[signals_width] 142 +[sst_expanded] 1 +[sst_vpaned_height] 317 +@200 +-Instruction +@c04023 +^>1 /matieres/3MMFMN/riscv32/bin/spike-dasm +TOP.svsimTestbench.dut.core.insn[31:0] +@29 +(0)TOP.svsimTestbench.dut.core.insn[31:0] +(1)TOP.svsimTestbench.dut.core.insn[31:0] +(2)TOP.svsimTestbench.dut.core.insn[31:0] +(3)TOP.svsimTestbench.dut.core.insn[31:0] +(4)TOP.svsimTestbench.dut.core.insn[31:0] +(5)TOP.svsimTestbench.dut.core.insn[31:0] +(6)TOP.svsimTestbench.dut.core.insn[31:0] +(7)TOP.svsimTestbench.dut.core.insn[31:0] +(8)TOP.svsimTestbench.dut.core.insn[31:0] +(9)TOP.svsimTestbench.dut.core.insn[31:0] +(10)TOP.svsimTestbench.dut.core.insn[31:0] +(11)TOP.svsimTestbench.dut.core.insn[31:0] +(12)TOP.svsimTestbench.dut.core.insn[31:0] +(13)TOP.svsimTestbench.dut.core.insn[31:0] +(14)TOP.svsimTestbench.dut.core.insn[31:0] +(15)TOP.svsimTestbench.dut.core.insn[31:0] +(16)TOP.svsimTestbench.dut.core.insn[31:0] +(17)TOP.svsimTestbench.dut.core.insn[31:0] +(18)TOP.svsimTestbench.dut.core.insn[31:0] +(19)TOP.svsimTestbench.dut.core.insn[31:0] +(20)TOP.svsimTestbench.dut.core.insn[31:0] +(21)TOP.svsimTestbench.dut.core.insn[31:0] +(22)TOP.svsimTestbench.dut.core.insn[31:0] +(23)TOP.svsimTestbench.dut.core.insn[31:0] +(24)TOP.svsimTestbench.dut.core.insn[31:0] +(25)TOP.svsimTestbench.dut.core.insn[31:0] +(26)TOP.svsimTestbench.dut.core.insn[31:0] +(27)TOP.svsimTestbench.dut.core.insn[31:0] +(28)TOP.svsimTestbench.dut.core.insn[31:0] +(29)TOP.svsimTestbench.dut.core.insn[31:0] +(30)TOP.svsimTestbench.dut.core.insn[31:0] +(31)TOP.svsimTestbench.dut.core.insn[31:0] +@1401201 +-group_end +@22 +TOP.svsimTestbench.dut.core.pc[31:0] +TOP.svsimTestbench.dut.core.rd[4:0] +TOP.svsimTestbench.dut.core.rs1Value[31:0] +TOP.svsimTestbench.dut.core.rs2Value[31:0] +TOP.svsimTestbench.dut.core.wbVal[31:0] +@28 +TOP.svsimTestbench.dut.core.clock +TOP.svsimTestbench.dut.core.reset +@22 +TOP.svsimTestbench.dut.io_x31[31:0] +@28 +TOP.svsimTestbench.dut.io_valid_x31 +TOP.svsimTestbench.dut.core.rdValid +[pattern_trace] 1 +[pattern_trace] 0 diff --git a/common/fix_mem.awk b/common/fix_mem.awk new file mode 100644 index 0000000..f5e99df --- /dev/null +++ b/common/fix_mem.awk @@ -0,0 +1,27 @@ +BEGIN { + addr = 0 + first = 1 +} + +# Ligne adresse : @xxxx +/^@/ { + newAddr = strtonum("0x" substr($0, 2)) + if (first) { + print $0 # garde le premier @xxxx (normalement @0000) + first = 0 + } else { + while (addr < newAddr) { + print "00000000" + addr++ + } + } + next +} + +# Ligne(s) de données +{ + for (i = 1; i <= NF; i++) { + print $i + addr++ + } +} diff --git a/common/objtomem.awk b/common/objtomem.awk new file mode 100755 index 0000000..e9d05eb --- /dev/null +++ b/common/objtomem.awk @@ -0,0 +1,73 @@ +# On utilise désormais objdump -s pour se faciliter la vie +# Contenu de la section .text : +# 1000 930f0000 930f7000 930fc0ff 93004000 ......p.......@. +# 1010 938f0000 938f3000 938fd0ff 1301c0ff ......0......... +# 1020 930f3100 930fd1ff 9301f07f 938f1100 ..1............. +# 1030 13020080 930ff2ff 13030001 930f0301 ................ +# 1040 930300ff 938f03ff 37040080 1304f4ff ........7....... +# 1050 930f1400 9304f0ff 938f1400 938f2403 ..............$. +# 1060 eff05ffa .._. +# Contenu de la section .data : +# 1064 78563412 cacaefbe adde0000 00000000 xV4............. +# 1074 00000000 0000 ...... + +function swapbytes(drow) { + return substr(drow, 7, 2) substr(drow, 5, 2) substr(drow, 3, 2) substr(drow, 1, 2) +} + +function writemem(addr, section) { + printf("@%08x\n", addr) # Adresse formatée + # On a aligné sur un multiple de 8, let's go + for (i = 1; i < length(section); i += 8) { + print(substr(section, i, 8)); + } + print "deadc0de" # Balise de fin de section (constante) +} + +# Ligne donnant l'info de la section +# Toujours trouvée avant le reste +/^Contenu|^Contents/ { + if (content) { + writemem(start_addr, content) + } + # En toute logique on pourrait mettre là le nom de section, mais + # ça dépend de la locale, donc pour l'instant on laisse tomber + section = 1 + content = "" +} + +/^\s*[0-9a-f]+/ { + if (section) { + addr = strtonum("0x" $1) + start_addr = strtonum("0x" $1) / 4 + if (addr != start_addr * 4) { + print("Début de section non alignée : "addr) > "/dev/stderr" + exit + } + section = 0; + } + + for (i = 2; i <= NR; i++) { + v = $i + if (v ~ /[0-9a-f]{2,8}/) { + # Le dernier nombre peut avoir une taille 2, 4, 6, 8 + if (length(v) == 6) { + v = v "00" + } else if (length($i) == 4) { + v = v "0000" + } else if (length($i) == 2) { + v = v "000000" + } else if (length($i) != 8) { + # Pour la partie avec les '.' + continue; + } + content = content swapbytes(v) + } + } +} + +END { + if (content) { + writemem(start_addr, content) + } +} diff --git a/common/parse_report.py b/common/parse_report.py new file mode 100644 index 0000000..fbbb14c --- /dev/null +++ b/common/parse_report.py @@ -0,0 +1,98 @@ +import sys +import os + +def extractPrimitives(fileName, destName): + f = open(fileName, "r") + print("Needed primitives:") + print("------------------") + br = False + cnt = 0 + while (not br): + line = f.readline() + if 'Primitives' in line: + br = (cnt == 1) + cnt += 1 + f.readline() + f.readline() + line = f.readline() + while (line != "\n"): + print(line[:-1]) + line = f.readline() + f.close() + +def extractUtilization(fileName, destName): + f = open(fileName, "r") + print("Real utilization:") + print("-----------------") + for line in f.readlines(): + if 'Slice LUTs' in line: + number = line.split("|")[2].strip() + perc = line.split("|")[5].strip() + print("LUT6s : {} ({}%)".format(number, perc)) + if 'Register as Flip Flop' in line: + number = line.split("|")[2].strip() + perc = line.split("|")[5].strip() + print("Flops : {} ({}%)".format(number, perc)) + if ('Block RAM Tile' in line) and ('Note' not in line): + number = line.split("|")[2].strip() + perc = line.split("|")[5].strip() + print("BRAMs : {} ({}%)".format(number, perc)) + if 'DSPs' in line: + number = line.split("|")[2].strip() + perc = line.split("|")[5].strip() + print("DSPs : {} ({}%)".format(number, perc)) + f.close() + print("") + +def extractPathDelay(fileName): + f = open(fileName, "r") + for line in f.readlines(): + if 'Data Path Delay' in line: + delay = float(line.split(":")[1].strip().split(" ")[0].strip().split("ns")[0].strip()) + f.close() + return delay + f.close() + print("Did not found any Data Path in circuit") + return float("NaN") + +def extractClock(summaryFileName, timingFileName): + f = open(summaryFileName, "r") + clock = '' + slack = '' + for line in f.readlines(): + if 'Clock' in line: + try: + clock = float(line.split("|")[1].strip()) + except: + # Clock line was found in report, but not with a numeric value associated + # This is probably due to a circuit optimization where all sequential logic + # has been evinced - e.g. output stick to a constant, ... + pass + if 'Slack' in line: + try: + slack = float(line.split("|")[1].strip()) + except: + # Same + pass + f.close() + print("Timing analysis:") + print("----------------") + if (clock == '' or slack == ''): + clock = float("NaN") + slack = float("NaN") + path = extractPathDelay(timingFileName) + else: + path = (clock-slack) + freq = (1.0/(path*pow(10, -9)))/pow(10,6) + print("Clock : %4.2f ns" % clock) + print("Slack : %4.2f ns" % slack) + print("Path : %4.2f ns" % path) + print("Freq : %4.2f MHz" % freq) + +if __name__=="__main__": + if (len(sys.argv) != 4): + sys.exit("Usage python parse_report.py ") + print("\n--------- Résumé ----------\n") + extractClock(sys.argv[3], sys.argv[2]) + extractUtilization(sys.argv[1], sys.argv[3]) + extractPrimitives(sys.argv[1], sys.argv[3]) diff --git a/common/programFPGA.vivado.tcl b/common/programFPGA.vivado.tcl new file mode 100644 index 0000000..02707c9 --- /dev/null +++ b/common/programFPGA.vivado.tcl @@ -0,0 +1,24 @@ +# Inputs arguments +set BIT_FILE [lindex $argv 0] + +# Open hardware mode +open_hw + +# Connect target +connect_hw_server +refresh_hw_server +set targets [get_hw_targets] +puts "targets: ${targets}" +current_hw_target [lindex ${targets} 0] +open_hw_target + +# Select device +set devices [get_hw_devices] +puts "devices: ${devices}" + +# Program +set_property PROGRAM.FILE ${BIT_FILE} [lindex [get_hw_devices] 1] + +program_hw_devices +refresh_hw_device + diff --git a/common/programFPGA.xsdb.tcl b/common/programFPGA.xsdb.tcl new file mode 100644 index 0000000..ab8aaae --- /dev/null +++ b/common/programFPGA.xsdb.tcl @@ -0,0 +1,4 @@ +connect +target 4 +fpga download.bit +exit diff --git a/common/reportCriticalPaths.tcl b/common/reportCriticalPaths.tcl new file mode 100644 index 0000000..ca27424 --- /dev/null +++ b/common/reportCriticalPaths.tcl @@ -0,0 +1,28 @@ +proc report_critical_paths { filename } { + + # Open the specified output file in write mode + set FH [open $filename w] + # Write the current date and CSV format to a file header + puts $FH "#\n# File created on [clock format [clock seconds]]\n#\n" + puts $FH "Startpoint,Endpoint,Slack,#Levels,#LUTs" + # Collect details from the 50 worst timing paths for the current analysis + # The $path variable contains a Timing Path object. + foreach path [get_timing_paths -max_paths 500 -nworst 1] { + # Get the LUT cells of the timing paths + set luts [get_cells -filter {REF_NAME =~ LUT*} -of_object $path] + # Get the startpoint of the Timing Path object + set startpoint [get_property STARTPOINT_PIN $path] + # Get the endpoint of the Timing Path object + set endpoint [get_property ENDPOINT_PIN $path] + # Get the slack on the Timing Path object + set slack [get_property SLACK $path] + # Get the number of logic levels between startpoint and endpoint + set levels [get_property LOGIC_LEVELS $path] + # Save the collected path details to the CSV file + puts $FH "$startpoint,$endpoint,$slack,$levels,[llength $luts]" + } + # Close the output file + close $FH + puts "CSV file $filename has been created.\n" + return 0 +}; # End PROC diff --git a/common/synthese.vivado.tcl b/common/synthese.vivado.tcl new file mode 100644 index 0000000..c02fbea --- /dev/null +++ b/common/synthese.vivado.tcl @@ -0,0 +1,35 @@ +# Input arguments +set DEVICE [lindex $argv 0] +set TOP [lindex $argv 1] +set REP [lindex $argv 2] + +#lecture de tous les sv générés +set files [glob RTL/${REP}.${TOP}/*.sv] +read_verilog -sv $files +read_verilog -sv src/main/resources/vsrc/clk_wiz.v +read_verilog -sv src/main/resources/vsrc/dpram.v + +read_xdc common/clock.xdc +# Detect XPM memory +auto_detect_xpm + +# Start synthesis +synth_design -top ${TOP} -part ${DEVICE} -mode "out_of_context" +report_utilization -file ${TOP}_utilization.rpt +report_timing -file ${TOP}_timing.rpt +report_clocks + +get_ports * + +set filename "${TOP}_summary.rpt" +set fileId [open $filename "w"] +if { [get_clocks] != "" } { + puts -nonewline $fileId "Clock | " + puts $fileId [get_property -min PERIOD [get_clocks]]; +} +if { [get_timing_paths] != "" } { + puts -nonewline $fileId "Slack | " + puts $fileId [get_property SLACK [get_timing_paths]]; +} +close $fileId +exit diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..b19d4e1 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.9.7 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..5708f81 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +logLevel := Level.Warn diff --git a/src/main/resources/vsrc/clk_wiz.v b/src/main/resources/vsrc/clk_wiz.v new file mode 100644 index 0000000..e60d184 --- /dev/null +++ b/src/main/resources/vsrc/clk_wiz.v @@ -0,0 +1,200 @@ + +// file: design_1_clk_wiz_0_0.v +// (c) Copyright 2017-2018, 2023 Advanced Micro Devices, Inc. All rights reserved. +// +// This file contains confidential and proprietary information +// of AMD and is protected under U.S. and international copyright +// and other intellectual property laws. +// +// DISCLAIMER +// This disclaimer is not a license and does not grant any +// rights to the materials distributed herewith. Except as +// otherwise provided in a valid license issued to you by +// AMD, and to the maximum extent permitted by applicable +// law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND +// WITH ALL FAULTS, AND AMD HEREBY DISCLAIMS ALL WARRANTIES +// AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING +// BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON- +// INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and +// (2) AMD shall not be liable (whether in contract or tort, +// including negligence, or under any other theory of +// liability) for any loss or damage of any kind or nature +// related to, arising under or in connection with these +// materials, including for any direct, or any indirect, +// special, incidental, or consequential loss or damage +// (including loss of data, profits, goodwill, or any type of +// loss or damage suffered as a result of any action brought +// by a third party) even if such damage or loss was +// reasonably foreseeable or AMD had been advised of the +// possibility of the same. +// +// CRITICAL APPLICATIONS +// AMD products are not designed or intended to be fail- +// safe, or for use in any application requiring fail-safe +// performance, such as life-support or safety devices or +// systems, Class III medical devices, nuclear facilities, +// applications related to the deployment of airbags, or any +// other applications that could lead to death, personal +// injury, or severe property or environmental damage +// (individually and collectively, "Critical +// Applications"). Customer assumes the sole risk and +// liability of any use of AMD products in Critical +// Applications, subject only to applicable laws and +// regulations governing limitations on product liability. +// +// THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS +// PART OF THIS FILE AT ALL TIMES. +//---------------------------------------------------------------------------- +// User entered comments +//---------------------------------------------------------------------------- +// None +// +//---------------------------------------------------------------------------- +// Output Output Phase Duty Cycle Pk-to-Pk Phase +// Clock Freq (MHz) (degrees) (%) Jitter (ps) Error (ps) +//---------------------------------------------------------------------------- +// clk_out1__10.00000______0.000______50.0______197.700_____96.948 +// clk_out2__125.00000______0.000______50.0______119.348_____96.948 +// +//---------------------------------------------------------------------------- +// Input Clock Freq (MHz) Input Jitter (UI) +//---------------------------------------------------------------------------- +// __primary_________125.000____________0.010 + +`timescale 1ps/1ps + +module clk_wiz + + (// Clock in ports + // Clock out ports + output clk_out1, + output clk_out2, + // Status and control signals + input reset, + output locked, + input clk_in1 + ); + // Input buffering + //------------------------------------ +wire clk_in1_design_1_clk_wiz_0_0; +wire clk_in2_design_1_clk_wiz_0_0; + IBUF clkin1_ibufg + (.O (clk_in1_design_1_clk_wiz_0_0), + .I (clk_in1)); + + // Clocking PRIMITIVE + //------------------------------------ + + // Instantiation of the MMCM PRIMITIVE + // * Unused inputs are tied off + // * Unused outputs are labeled unused + + wire clk_out1_design_1_clk_wiz_0_0; + wire clk_out2_design_1_clk_wiz_0_0; + wire clk_out3_design_1_clk_wiz_0_0; + wire clk_out4_design_1_clk_wiz_0_0; + wire clk_out5_design_1_clk_wiz_0_0; + wire clk_out6_design_1_clk_wiz_0_0; + wire clk_out7_design_1_clk_wiz_0_0; + + wire [15:0] do_unused; + wire drdy_unused; + wire psdone_unused; + wire locked_int; + wire clkfbout_design_1_clk_wiz_0_0; + wire clkfbout_buf_design_1_clk_wiz_0_0; + wire clkfboutb_unused; + wire clkout0b_unused; + wire clkout1b_unused; + wire clkout2_unused; + wire clkout2b_unused; + wire clkout3_unused; + wire clkout3b_unused; + wire clkout4_unused; + wire clkout5_unused; + wire clkout6_unused; + wire clkfbstopped_unused; + wire clkinstopped_unused; + wire reset_high; + + MMCME2_ADV + #(.BANDWIDTH ("OPTIMIZED"), + .CLKOUT4_CASCADE ("FALSE"), + .COMPENSATION ("ZHOLD"), + .STARTUP_WAIT ("FALSE"), + .DIVCLK_DIVIDE (1), + .CLKFBOUT_MULT_F (8.000), + .CLKFBOUT_PHASE (0.000), + .CLKFBOUT_USE_FINE_PS ("FALSE"), + .CLKOUT0_DIVIDE_F (100.000), + .CLKOUT0_PHASE (0.000), + .CLKOUT0_DUTY_CYCLE (0.500), + .CLKOUT0_USE_FINE_PS ("FALSE"), + .CLKOUT1_DIVIDE (8), + .CLKOUT1_PHASE (0.000), + .CLKOUT1_DUTY_CYCLE (0.500), + .CLKOUT1_USE_FINE_PS ("FALSE"), + .CLKIN1_PERIOD (8.000)) + mmcm_adv_inst + // Output clocks + ( + .CLKFBOUT (clkfbout_design_1_clk_wiz_0_0), + .CLKFBOUTB (clkfboutb_unused), + .CLKOUT0 (clk_out1_design_1_clk_wiz_0_0), + .CLKOUT0B (clkout0b_unused), + .CLKOUT1 (clk_out2_design_1_clk_wiz_0_0), + .CLKOUT1B (clkout1b_unused), + .CLKOUT2 (clkout2_unused), + .CLKOUT2B (clkout2b_unused), + .CLKOUT3 (clkout3_unused), + .CLKOUT3B (clkout3b_unused), + .CLKOUT4 (clkout4_unused), + .CLKOUT5 (clkout5_unused), + .CLKOUT6 (clkout6_unused), + // Input clock control + .CLKFBIN (clkfbout_buf_design_1_clk_wiz_0_0), + .CLKIN1 (clk_in1_design_1_clk_wiz_0_0), + .CLKIN2 (1'b0), + // Tied to always select the primary input clock + .CLKINSEL (1'b1), + // Ports for dynamic reconfiguration + .DADDR (7'h0), + .DCLK (1'b0), + .DEN (1'b0), + .DI (16'h0), + .DO (do_unused), + .DRDY (drdy_unused), + .DWE (1'b0), + // Ports for dynamic phase shift + .PSCLK (1'b0), + .PSEN (1'b0), + .PSINCDEC (1'b0), + .PSDONE (psdone_unused), + // Other control and status signals + .LOCKED (locked_int), + .CLKINSTOPPED (clkinstopped_unused), + .CLKFBSTOPPED (clkfbstopped_unused), + .PWRDWN (1'b0), + .RST (reset_high)); + assign reset_high = reset; + + assign locked = locked_int; +// Clock Monitor clock assigning +//-------------------------------------- + // Output buffering + //----------------------------------- + + BUFG clkf_buf + (.O (clkfbout_buf_design_1_clk_wiz_0_0), + .I (clkfbout_design_1_clk_wiz_0_0)); + + BUFG clkout1_buf + (.O (clk_out1), + .I (clk_out1_design_1_clk_wiz_0_0)); + + + BUFG clkout2_buf + (.O (clk_out2), + .I (clk_out2_design_1_clk_wiz_0_0)); + +endmodule diff --git a/src/main/resources/vsrc/dpram.v b/src/main/resources/vsrc/dpram.v new file mode 100644 index 0000000..d66fe04 --- /dev/null +++ b/src/main/resources/vsrc/dpram.v @@ -0,0 +1,72 @@ +// Template pour la lecture d'un fichier à la simulation et +// la génération de block rams à la synthèse +// https://docs.amd.com/r/en-US/ug901-vivado-synthesis/Byte-Write-Enable-True-Dual-Port-with-Byte-Wide-Write-Enable-Verilog +// True-Dual-Port BRAM with Byte-wide Write Enable +// Read-First mode + +module dpram +#( + //-------------------------------------------------------------------------- + parameter NUM_COL = 4, + parameter COL_WIDTH = 8, + parameter ADDR_WIDTH = 10, + // Addr Width in bits : 2 *ADDR_WIDTH = RAM Depth + parameter DATA_WIDTH = NUM_COL*COL_WIDTH, // Data Width in bits + parameter INIT_FILE = "program.mem" + //---------------------------------------------------------------------- +) ( + input clkA, + input enaA, + input [NUM_COL-1:0] weA, + input [ADDR_WIDTH-1:0] addrA, + input [DATA_WIDTH-1:0] dinA, + output reg [DATA_WIDTH-1:0] doutA, + + input clkB, + input enaB, + input [NUM_COL-1:0] weB, + input [ADDR_WIDTH-1:0] addrB, + input [DATA_WIDTH-1:0] dinB, + output reg [DATA_WIDTH-1:0] doutB + ); + + + // Core Memory + (* ram_style = "block" *) reg [DATA_WIDTH-1:0] ram_block [(2**ADDR_WIDTH)-1:0]; + + // Initialization + initial begin + if (INIT_FILE != "NONE") begin + $readmemh(INIT_FILE, ram_block); + end + end + + integer i; + // Port-A Operation + always @ (posedge clkA) begin + if(enaA) begin + for(i=0;i [constructor args...]") + sys.exit(1) + } + + val moduleName = args(0) + val ctorArgs = args.drop(1) // reste des arguments (strings) + + // Reflection scala : charger dynamiquement un module + val cls = Class.forName(moduleName) + val ctor = cls.getConstructors.head + + // Fonction de conversion string -> objet typé + def parseArg(s: String): AnyRef = { + // essai en Int + if (s.matches("^-?\\d+$")) { + Integer.valueOf(s.toInt) + } + // essai en Bool + else if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false")) { + java.lang.Boolean.valueOf(s.toBoolean) + } + // sinon, on laisse en String + else { + s + } + } + + val generator = () => { + if (ctorArgs.isEmpty) { + ctor.newInstance().asInstanceOf[RawModule] + } else { + val argsObjects: Array[Object] = ctorArgs.map(parseArg).toArray + ctor.newInstance(argsObjects: _*).asInstanceOf[RawModule] + } + } + + ChiselStage.emitSystemVerilogFile( + gen = generator(), + firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info"), + args = Array("--target-dir", s"RTL/$moduleName") + ) + } +} diff --git a/src/main/scala/projet/BusInterconnect.scala b/src/main/scala/projet/BusInterconnect.scala new file mode 100644 index 0000000..cd6ac9c --- /dev/null +++ b/src/main/scala/projet/BusInterconnect.scala @@ -0,0 +1,89 @@ +package projet + +import chisel3._ +import chisel3.util._ + +// Bus très simple : au temps t le maître fait une requête +// au temps t+1, il a la réponse, sinon c'est mort ! + +// Signaux utilisés par le bus, coté esclave, on fait un +// Flip pour l'avoir coté maître ensuite +class BusInterface extends Bundle { + val addr = Input(UInt(32.W)) // adresse cible de la transaction + val wdata = Input(UInt(32.W)) // donnée en écriture (max 32 bits) + val rdata = Output(UInt(32.W)) // donnée lue de l'esclave, avec 1 cycle de retard + val be = Input(Vec(4, Bool())) // byte-enable pour les accès écriture mémoire + val en = Input(Bool()) // enable de la mémoire, read si be.orR est égal à 0, écriture sinon +} + +class BusInterconnect extends Module { + val io = IO(new Bundle { + val master = new BusInterface // Bus avec un unique maître + val dmem = Flipped(new BusInterface) // et plusieurs esclaves + val ldip = Flipped(new BusInterface) + val clnt = Flipped(new BusInterface) + val vmem = Flipped(new BusInterface) + }) + + // On récupère l'adresse émise par le maître pour trouver à quel + // esclave elle fait référence + val addr = io.master.addr + + // On reprend nos bonnes vielles adresses, pas très efficaces, mais ça fait le job + // -- Slave 0 1 2 3 4 5 + // -- Name RAM prog | IP led | IP pin | IP PLIC | IP_CLINT | DDR + // BASE => ( X"0000_1000", X"3000_0000", X"3000_0008", X"0C00_0000", X"0200_0000", X"8000_0000"), + // HIGH => ( X"0000_8FFF", X"3000_0004", X"3000_0008", X"1000_0000", X"0200_C000", X"8FFF_FFFF") + + val DMEM_BASE = 0x00000000L.U // Une mémoire unifiée code + données + val DMEM_SIZE = 0x00010000L.U + + val LDIP_BASE = 0x30000000L.U + val LDIP_SIZE = 0x00000004L.U + + val CLNT_BASE = 0x02000000L.U + val CLNT_SIZE = 0x0000C000L.U + + val VMEM_BASE = 0x80000000L.U // Mémoire vidéo dual port + val VMEM_SIZE = 0x00020000L.U + + val is_dmem = addr >= DMEM_BASE && addr < (DMEM_BASE + DMEM_SIZE) + val is_ldip = addr >= LDIP_BASE && addr < (LDIP_BASE + LDIP_SIZE) + val is_clnt = addr >= CLNT_BASE && addr < (CLNT_BASE + CLNT_SIZE) + val is_vmem = addr >= VMEM_BASE && addr < (VMEM_BASE + VMEM_SIZE) + + // On connecte directement ce qui doit l'être en faisant l'hypothèse que + io.dmem.en := io.master.en && is_dmem + io.ldip.en := io.master.en && is_ldip + io.clnt.en := io.master.en && is_clnt + io.vmem.en := io.master.en && is_vmem + // la chose connectée à ses adresses qui commencent à zéro + io.dmem.addr := io.master.addr - DMEM_BASE + io.ldip.addr := io.master.addr - LDIP_BASE + io.clnt.addr := io.master.addr - CLNT_BASE + io.vmem.addr := io.master.addr - VMEM_BASE + io.dmem.wdata := io.master.wdata + io.ldip.wdata := io.master.wdata + io.clnt.wdata := io.master.wdata + io.vmem.wdata := io.master.wdata + io.dmem.be := io.master.be + io.ldip.be := io.master.be + io.clnt.be := io.master.be + io.vmem.be := io.master.be + + // On doit décaler d'un cycle (latence de l'accès mémoire) + // les signaux qui multiplexent la donnée en sortie + val is_pdmem = RegNext(is_dmem) + val is_pldip = RegNext(is_ldip) + val is_pclnt = RegNext(is_clnt) + val is_pvmem = RegNext(is_vmem) + + io.master.rdata := MuxCase(0x0BADADDL.U(32.W), + Seq( + is_pdmem -> io.dmem.rdata, + is_pldip -> io.ldip.rdata, + is_pclnt -> io.clnt.rdata, + is_pvmem -> io.vmem.rdata + ) + ) +} diff --git a/src/main/scala/projet/CLint.scala b/src/main/scala/projet/CLint.scala new file mode 100644 index 0000000..184ba14 --- /dev/null +++ b/src/main/scala/projet/CLint.scala @@ -0,0 +1,56 @@ +package projet +import chisel3._ + +/*\ + * Principalement le timer pour pouvoir faire tourner space invaders ! +\*/ +class CLint extends Module { + val io = IO(new Bundle { + val bus = new BusInterface + val tip = Output(Bool()) + }) + + // Adresses des deux registres 64 bits du composant + val MTIMERCMP_ADDR = 0x4000 + val MTIMER_ADDR = 0xBFF8 + + // Sur le rv32i le timer est constitué de 2 registres pour faire 64 bits + val mtime_lo = RegInit(0.U(32.W)) + val mtime_hi = RegInit(0.U(32.W)) + + // Free running, comme on dit chez nous ! + mtime_lo := mtime_lo + 1.U + mtime_hi := Mux(mtime_lo + 1.U === 0.U, mtime_hi + 1.U, mtime_hi) + + // La comparaison nécessite également 2 registres, pour une + // comparaison sur 64 bits si nécessaire + val mtimecmp_lo = RegInit(0.U(32.W)) + val mtimecmp_hi = RegInit(0.U(32.W)) + // Sortie mtimer interrupt pending dont on se moque pour l'instant + io.tip := (mtime_hi > mtimecmp_hi) || ((mtime_hi === mtimecmp_hi) && (mtime_lo >= mtimecmp_lo)) + // Valeur par défaut sur le bus de lecture + // avec toujours un cycle de latence + val clintRegValue = RegInit(0.U(32.W)) + + io.bus.rdata := clintRegValue + + when(io.bus.en === true.B) { + when (io.bus.be.asUInt.orR) { // Écriture + when(io.bus.addr === MTIMERCMP_ADDR.U) { + mtimecmp_lo := io.bus.wdata + } .elsewhen(io.bus.addr === (MTIMERCMP_ADDR + 4).U) { + mtimecmp_hi := io.bus.wdata + } + } .otherwise { // Lecture + when(io.bus.addr === MTIMER_ADDR.U) { + clintRegValue := mtime_lo + } .elsewhen(io.bus.addr === (MTIMER_ADDR + 4).U) { + clintRegValue := mtime_hi + } .elsewhen(io.bus.addr === MTIMERCMP_ADDR.U) { + clintRegValue := mtimecmp_lo + } .elsewhen(io.bus.addr === (MTIMERCMP_ADDR + 4).U) { + clintRegValue := mtimecmp_hi + } + } + } +} diff --git a/src/main/scala/projet/ClockGen.scala b/src/main/scala/projet/ClockGen.scala new file mode 100644 index 0000000..5212088 --- /dev/null +++ b/src/main/scala/projet/ClockGen.scala @@ -0,0 +1,64 @@ +package projet + +import chisel3._ +import chisel3.util._ + +/*\ + * Interface chisel du fichier généré par le clock_wizard + * de Vivado pour générer une clock à 10 MHz + * Le nom *doit* être celui qui est dans le systemverilog + * (qui est dans TP/common/clk_wiz_0_clk_wiz.sv) +\*/ +class clk_wiz extends BlackBox with HasBlackBoxResource { + + addResource("/vsrc/clk_wiz.v") + + val io = IO(new Bundle { + val clk_in1 = Input(Clock()) + val reset = Input(Reset()) + val clk_out1 = Output(Clock()) // 10 MHz + val clk_out2 = Output(Clock()) // 125 MHz + val locked = Output(Bool()) + }) +} + +class ClockGen(val divBy: Int = 10, useSim: Boolean = false) extends Module { + val io = IO(new Bundle { + val clk_in1 = Input(Clock()) + val reset = Input(Reset()) + val clk_out1 = Output(Clock()) + val clk_out2 = Output(Clock()) + val locked = Output(Bool()) + }) + + val maxCnt = (divBy - 1).U + val midCnt = ((divBy / 2) - 1).U + + if (useSim) { + val cntReg = RegInit(0.U(log2Ceil(divBy).W)) + cntReg := cntReg + 1.U + val outReg = RegInit(false.B) + val rstReg = RegInit(true.B) + + rstReg := Mux(io.reset.asBool, true.B, rstReg) + + io.locked := !rstReg + + when (cntReg === midCnt) { + outReg := true.B + } .elsewhen (cntReg === maxCnt) { + outReg := false.B + cntReg := 0.U + rstReg := false.B + } + io.clk_out1 := outReg.asClock + io.clk_out2 := io.clk_in1 + } else { + val ckgen = Module(new clk_wiz) + ckgen.io.clk_in1 := io.clk_in1 + ckgen.io.reset := io.reset + io.clk_out1 := ckgen.io.clk_out1 + io.clk_out2 := ckgen.io.clk_out2 + io.locked := ckgen.io.locked + } +} diff --git a/src/main/scala/projet/Compteur.scala b/src/main/scala/projet/Compteur.scala new file mode 100644 index 0000000..87bdd07 --- /dev/null +++ b/src/main/scala/projet/Compteur.scala @@ -0,0 +1,35 @@ +package projet +import chisel3._ + +// Déclaration d'un classe Compteur très générique +class Compteur( + n: Int, // Le paramètre n permet de fixer le nombre de bits du compteur. + hasEnable: Boolean = false, // Le paramètre hasEnable permet d'ajouter une entrée optionnelle pour autoriser l'incrémentation. + hasMax: Boolean = false // Le paramètre hasMax permet d'ajouter : - une entrée optionnelle max, qui fait reboucler le compteur après qu'il est atteint la valeur fournie. + // - une sortie optionnelle atMax, qui vaut 1 lorsque le compteur atteint la valeur max définie. + ) extends Module { + val io = IO(new Bundle { + // Grâce au parametre hasEnable, on peut activer optionnellement le port d'entrée en. + // En scala, la classe option a 2 classes dérivées : Some et None. + // Some permet donc de wrapper notre type optionnel. + val en = if (hasEnable) Some(Input(Bool())) else None + val max = if (hasMax) Some(Input(UInt(n.W))) else None + val atMax = if (hasMax) Some(Output(Bool())) else None + val out = Output(UInt(n.W)) // On utilise le paramètre n pour remplacer la constante de l'exercice prédédent. + }) + + val compteur = RegInit(0.U(n.W)) + + // condition d'incrément + val doInc = if (hasEnable) io.en.get else true.B // Attention, notez qu'il faut utiliser l'opérateur get pour récupérer l'option wrappée par le Some. + + + // condition d'égalité au maximum + val atMax = if (hasMax) compteur === io.max.get else false.B + + compteur := Mux(doInc, Mux(atMax, 0.U(n.W), compteur + 1.U), compteur) + + if (hasMax) io.atMax.get := atMax + + io.out := compteur +} diff --git a/src/main/scala/projet/GeneSync.scala b/src/main/scala/projet/GeneSync.scala new file mode 100644 index 0000000..930bec8 --- /dev/null +++ b/src/main/scala/projet/GeneSync.scala @@ -0,0 +1,63 @@ +package projet +import chisel3._ + +class GeneSync extends Module { + val io = IO(new Bundle { + val hsync = Output(Bool()) + val vsync = Output(Bool()) + val img = Output(Bool()) + val x = Output(UInt(10.W)) + val y = Output(UInt(9.W)) + }) + + // Paramètres de synchronisation horizontale et verticale + val synchroX = VecInit(Seq( + 95.U, // HS pulse width + 47.U, // horizontal back porch + 639.U, // horizontal display time + 15.U // horizontal front porch + )) + + val synchroY = VecInit(Seq( + 1.U, // VS pulse width + 28.U, // vertical back porch + 479.U, // vertical display time + 9.U // vertical front porch + )) + + // Instanciation des compteurs + val compteurCLK = Module(new Compteur(3, hasMax = true)) // Compteur horloge (3 bits) + val comptX = Module(new Compteur(10, hasEnable = true, hasMax = true)) // Compteur horizontal (10 bits) + val comptY = Module(new Compteur(9, hasEnable = true, hasMax = true)) // Compteur vertical (9 bits) + val phaseH = Module(new Compteur(2, hasEnable = true)) // Phases horizontales (2 bits) + val phaseV = Module(new Compteur(2, hasEnable = true)) // Phases verticales (2 bits) + + // Signaux d'activation + val en25 = compteurCLK.io.atMax.get // Activation à 25MHz quand compteurCLK atteint 4 + val enPhaseH = en25 && comptX.io.atMax.get + val enY = enPhaseH && phaseH.io.out === 3.U // Nouvelle ligne + + // Configuration compteur CLK (divise 125MHz par 5 pour avoir 25MHz) + compteurCLK.io.max.get := 4.U + + // Configuration compteur X (horizontal) + comptX.io.en.get := en25 + comptX.io.max.get := synchroX(phaseH.io.out) + + // Configuration compteur Y (vertical) + comptY.io.en.get := enY + comptY.io.max.get := synchroY(phaseV.io.out) + + // Configuration compteurs de phases + phaseH.io.en.get := enPhaseH + phaseV.io.en.get := enY && comptY.io.atMax.get + + // Génération des signaux de sortie + io.hsync := phaseH.io.out.orR // true si phaseH != 0 (pas en phase pulse) + io.vsync := phaseV.io.out.orR // true si phaseV != 0 (pas en phase pulse) + io.img := (phaseH.io.out === 2.U) && (phaseV.io.out === 2.U) // Zone d'affichage + + // Coordonnées pixel + io.x := comptX.io.out + io.y := comptY.io.out +} diff --git a/src/main/scala/projet/IDMem.scala b/src/main/scala/projet/IDMem.scala new file mode 100644 index 0000000..95e435b --- /dev/null +++ b/src/main/scala/projet/IDMem.scala @@ -0,0 +1,164 @@ +package projet + +import chisel3._ +import chisel3.util._ +import chisel3.experimental._ + +/* + * Blackbox Verilog pour la simu et la synthèse sans placement + */ +class dpram( // nom identique à celui du verilog + val NUM_COL: Int, + val COL_WIDTH: Int, + val ADDR_WIDTH: Int, + val DATA_WIDTH: Int, + val INIT_FILE: String = "program.mem" +) extends BlackBox(Map( + "NUM_COL" -> IntParam(NUM_COL), + "COL_WIDTH" -> IntParam(COL_WIDTH), + "ADDR_WIDTH" -> IntParam(ADDR_WIDTH), + "DATA_WIDTH" -> IntParam(DATA_WIDTH), + "INIT_FILE" -> StringParam(if (INIT_FILE != "") INIT_FILE else "NONE") +)) with HasBlackBoxResource { + + require(DATA_WIDTH == NUM_COL * COL_WIDTH, "DATA_WIDTH must be equal to NUM_COL*COL_WIDTH.") + + addResource("/vsrc/dpram.v") + + val io = IO(new Bundle { + // Port A + val clkA = Input(Clock()) + val enaA = Input(Bool()) + val addrA = Input(UInt(ADDR_WIDTH.W)) + val doutA = Output(UInt(DATA_WIDTH.W)) + val dinA = Input(UInt(DATA_WIDTH.W)) + val weA = Input(UInt(NUM_COL.W)) + // Port B + val clkB = Input(Clock()) + val enaB = Input(Bool()) + val addrB = Input(UInt(ADDR_WIDTH.W)) + val doutB = Output(UInt(DATA_WIDTH.W)) + val dinB = Input(UInt(DATA_WIDTH.W)) + val weB = Input(UInt(NUM_COL.W)) + }) +} + +class xpm_memory_tdpram( // nom identique à celui de Xilinx + val ADDR_WIDTH: Int, + val INIT_FILE: String + ) extends BlackBox(Map( + "ADDR_WIDTH_A" -> IntParam(ADDR_WIDTH), + "ADDR_WIDTH_B" -> IntParam(ADDR_WIDTH), + "BYTE_WRITE_WIDTH_A" -> 8, + "MEMORY_INIT_FILE" -> StringParam(INIT_FILE), + "MEMORY_PRIMITIVE" -> "block", + "MEMORY_SIZE" -> 524288, + "READ_DATA_WIDTH_A" -> 32, + "READ_DATA_WIDTH_B" -> 32, + "READ_LATENCY_A" -> 1, + "READ_LATENCY_B" -> 1, + "WRITE_DATA_WIDTH_A" -> 32, + "WRITE_DATA_WIDTH_B" -> 32 + )) with HasBlackBoxResource { + val io = IO(new Bundle { + // Port A + val clka = Input(Clock()) + val rsta = Input(Bool()) + val ena = Input(Bool()) + val regcea = Input(Bool()) + val dina = Input(UInt(32.W)) + val addra = Input(UInt(ADDR_WIDTH.W)) + val wea = Input(UInt(4.W)) + val injectdbiterra = Input(Bool()) + val injectsbiterra = Input(Bool()) + val douta = Output(UInt(32.W)) +// val dbiterrA = Output(Bool()) +// val sbiterrA = Output(Bool()) + // Port B + val clkb = Input(Clock()) + val rstb = Input(Bool()) + val enb = Input(Bool()) + val regceb = Input(Bool()) + val dinb = Input(UInt(32.W)) + val addrb = Input(UInt(ADDR_WIDTH.W)) + val web = Input(UInt(4.W)) + val injectdbiterrb = Input(Bool()) + val injectsbiterrb = Input(Bool()) + val doutb = Output(UInt(32.W)) +// val dbiterrB = Output(Bool()) +// val sbiterrB = Output(Bool()) + val sleep = Input(Bool()) + }) +} +/*\ + * Attention, les BRAM sont adressables en mots, et les adresses émises + * sur notre interconnect sont des adresses octets, qui en revanche + * doivent être alignées sur le type de la donnée transportée. + * Nous optons ici pour une mémoire dual-port pour l'accès concurrent + * aux instructions et aux données, avec un espace d'adressage unique + * pour faciliter le préchargement (on espère). + * +\*/ +class IDMem(sim: Boolean, addrWidth: Int, memFile: String ="") extends Module { + val io = IO(new Bundle { + val clki = Input(Clock()) + val ibus = new BusInterface + val clkd = Input(Clock()) + val dbus = new BusInterface + }) + +// Pour la simulation ou la synthèse ne nécessitant pas de repérer les BRAM, on blackboxe avec le verilog Xilinx + if (sim || memFile == "") { + // Mémoire 32 bits : 4 colonnes de 8 bit + // l'accès BRAM est un accès mot, et non octet, on ignore les 2 bits de + // poids faible de l'adresse + + val unifiedRam = Module(new dpram( + NUM_COL = 4, + COL_WIDTH = 8, + ADDR_WIDTH = addrWidth - 2, + DATA_WIDTH = 32, + INIT_FILE = memFile + )) + + unifiedRam.io.clkA := io.clki + unifiedRam.io.clkB := io.clkd + // instructions sur le port a + unifiedRam.io.enaA := io.ibus.en + unifiedRam.io.addrA := io.ibus.addr(addrWidth - 1, 2) + io.ibus.rdata := unifiedRam.io.doutA + unifiedRam.io.weA := 0.U + unifiedRam.io.dinA := 0.U + // données sur le port b + unifiedRam.io.enaB := io.dbus.en + unifiedRam.io.addrB := io.dbus.addr(addrWidth - 1, 2) + io.dbus.rdata := unifiedRam.io.doutB + unifiedRam.io.weB := io.dbus.be.asUInt + unifiedRam.io.dinB := io.dbus.wdata + } else { + val dpram = Module(new xpm_memory_tdpram(ADDR_WIDTH = addrWidth - 2,INIT_FILE = memFile)) + dpram.io.clka := clock + dpram.io.rsta := 0.U + dpram.io.ena := io.dbus.en + dpram.io.regcea := false.B + dpram.io.dina := io.dbus.wdata + dpram.io.addra := io.dbus.addr(addrWidth - 1, 2) + dpram.io.wea := io.dbus.be.asUInt + dpram.io.injectdbiterra := false.B + dpram.io.injectsbiterra := false.B + io.dbus.rdata := dpram.io.douta + + dpram.io.clkb := clock + dpram.io.rstb := 0.U + dpram.io.enb := io.ibus.en + dpram.io.regceb := false.B + dpram.io.dinb := 0.U + dpram.io.addrb := io.ibus.addr(addrWidth - 1, 2) + dpram.io.web := 0.U + dpram.io.injectdbiterrb := false.B + dpram.io.injectsbiterrb := false.B + io.ibus.rdata := dpram.io.doutb + + dpram.io.sleep := false.B + } +} diff --git a/src/main/scala/projet/LedIp.scala b/src/main/scala/projet/LedIp.scala new file mode 100644 index 0000000..21b85bb --- /dev/null +++ b/src/main/scala/projet/LedIp.scala @@ -0,0 +1,46 @@ +package projet +import chisel3._ +import chisel3.util._ + +/*\ + * Fusion de IP_LED et IP_PIN du projet VHDL +\*/ +class LedIp extends Module { + val io = IO(new Bundle { + val x31 = Input(UInt(32.W)) + val switch = Input(UInt(4.W)) + val led = Output(UInt(4.W)) + val button = Input(UInt(3.W)) + val bus = new BusInterface + }) + + io.bus.rdata := 0x01ed01ed.U // On lira ça si on lit ! + + val dinReg = RegInit(0.U(32.W)) + val x31Reg = RegNext(io.x31) // On lit x31 en continue + val btnReg = Reg(UInt(32.W)) + + // Le cpu fait une requête + // On se moque de l'adresse ... + // L'écriture écrit dans le registre, la lecture retourne l'état des boutons + when (io.bus.en === true.B) { + when (io.bus.be.asUInt.orR) { // Écriture dans le registre des leds internes + dinReg := io.bus.wdata + } .otherwise { // Lecture des boutons poussoirs, pas de débounce car lus par le soft + btnReg := Fill(29, 0.U) ## io.button + } + } + + io.bus.rdata := btnReg + + // On échantillonne la donnée d'entrée en continue + // C'est soit une donnée venant du registre x31 du + // proc, si le dernier switch est à 1, soit le registre écrit par le soft + val data = Mux(io.switch(3).asBool, x31Reg, dinReg) + + // On l'affiche + io.led := MuxLookup(io.switch(2, 0), 0.U(4.W))( + (0 until 8).map { i => + (i.U -> data((i * 4) + 3, i * 4)) + }) +} diff --git a/src/main/scala/projet/Rv32i.scala b/src/main/scala/projet/Rv32i.scala new file mode 100644 index 0000000..fcbc09c --- /dev/null +++ b/src/main/scala/projet/Rv32i.scala @@ -0,0 +1,14 @@ +package projet + +import chisel3._ + +class Rv32i(sim: Boolean = true) extends Module { + val io = IO(new Bundle { + val ibus = Flipped(new BusInterface) + val dbus = Flipped(new BusInterface) + val x31 = Output(UInt(32.W)) + val valid_x31 = if (sim) Some(Output(Bool())) else None + }) + + locally(sim) +} diff --git a/src/main/scala/projet/VgaIp.scala b/src/main/scala/projet/VgaIp.scala new file mode 100644 index 0000000..014e9dc --- /dev/null +++ b/src/main/scala/projet/VgaIp.scala @@ -0,0 +1,40 @@ +package projet + +import chisel3._ +import chisel3.util._ + +class VgaIp extends Module { + val io = IO(new Bundle { + val clk = Input(Clock()) + val bus = Flipped(new BusInterface) + val hs = Output(Bool()) + val vs = Output(Bool()) + val r = Output(UInt(5.W)) + val g = Output(UInt(6.W)) + val b = Output(UInt(5.W)) + }) + + withClock(io.clk) { + val sync = Module(new GeneSync()) + io.hs := RegNext(sync.io.hsync) + io.vs := RegNext(sync.io.vsync) + val img_sync = RegNext(sync.io.img) + val index = RegNext(sync.io.x(2, 1)) + + io.bus.en := true.B + io.bus.be := VecInit(false.B, false.B, false.B, false.B) + io.bus.wdata := 0.U + val addr = 80.U * sync.io.y(8, 1) + sync.io.x(9, 3) + // Adresse mot dans la ram vidéo + io.bus.addr := addr(14, 0) ## 0.U(2.W) + + val pixel = MuxCase(0.U, + (0 until 4).map {i => + (index === i.U) -> io.bus.rdata(8 * (3 - i) + 7, 8 * (3 - i)) + }) + + io.r := Mux(img_sync, pixel(7, 6)<<3, 0.U) + io.g := Mux(img_sync, pixel(5, 3)<<3, 0.U) + io.b := Mux(img_sync, pixel(2, 0)<<2, 0.U) + } +} diff --git a/src/main/scala/projet/ZzTop.scala b/src/main/scala/projet/ZzTop.scala new file mode 100644 index 0000000..54fc500 --- /dev/null +++ b/src/main/scala/projet/ZzTop.scala @@ -0,0 +1,74 @@ +package projet + +import chisel3._ + +class ZzTop(file: String ="",sim: Boolean = true) extends Module { + val io = IO(new Bundle { + // Interface debug minimale + val switch = Input(UInt(4.W)) + val led = Output(UInt(4.W)) + val push = Input(UInt(3.W)) + // Debug simu pour ne pas s'embeter avec le switch dans le testbench + val x31 = if (sim) Some(Output(UInt(32.W))) else None + val valid_x31 = if (sim) Some(Output(Bool())) else None + + // Port vga + val hs = Output(Bool()) + val vs = Output(Bool()) + val r = Output(UInt(5.W)) + val g = Output(UInt(6.W)) + val b = Output(UInt(5.W)) + }) + + val clkg = Module(new ClockGen(12, sim)) // ~ 10 MHz + // clock et reset sont les signaux implicites + clkg.io.clk_in1 := clock + clkg.io.reset := reset + val rst = (reset.asBool || !clkg.io.locked).asAsyncReset + + // Ces deux IPs doivent tourner à 125 MHz + val vmem = Module(new IDMem(sim,17)) + vmem.io.clki := clkg.io.clk_out2 // Clock du pixel + vmem.io.clkd := clkg.io.clk_out1 // Clock du reste du système + val vgad = Module(new VgaIp) + vgad.io.clk := clkg.io.clk_out2 // Clock pixel + + withClockAndReset(clkg.io.clk_out1, rst) { + val interconnect = Module(new BusInterconnect) + val core = Module(new Rv32i(sim)) + val dmem = Module(new IDMem(sim,16,file)) + val ldip = Module(new LedIp) + val clnt = Module(new CLint) + + dmem.io.clki := clkg.io.clk_out1 // Ou clock, non ? + dmem.io.clkd := clkg.io.clk_out1 // + + ldip.io.switch := io.switch + ldip.io.button := io.push + ldip.io.x31 := core.io.x31 + io.led := ldip.io.led + + if (sim){ + io.x31.get := core.io.x31 + io.valid_x31.get := core.io.valid_x31.get + } + + + interconnect.io.master <> core.io.dbus + + interconnect.io.dmem <> dmem.io.dbus + interconnect.io.ldip <> ldip.io.bus + interconnect.io.clnt <> clnt.io.bus + interconnect.io.vmem <> vmem.io.dbus + + // connexion ad-hoc pour le bus d'instructions + dmem.io.ibus <> core.io.ibus + // connexion ad-hoc pour l'IP Vga + vmem.io.ibus <> vgad.io.bus + io.hs := vgad.io.hs + io.vs := vgad.io.vs + io.r := vgad.io.r + io.g := vgad.io.g + io.b := vgad.io.b + } +} diff --git a/src/test/scala/projet/ZzTopSpec.scala b/src/test/scala/projet/ZzTopSpec.scala new file mode 100644 index 0000000..6cfa2ef --- /dev/null +++ b/src/test/scala/projet/ZzTopSpec.scala @@ -0,0 +1,101 @@ +package projet + +import chisel3.simulator.scalatest.ChiselSim +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.must.Matchers +import org.scalatest.prop.TableDrivenPropertyChecks._ +import java.nio.file.{Files, Paths} +import scala.jdk.CollectionConverters._ +import scala.io.Source +import scala.collection.mutable.ListBuffer + +class ZzTopSpec extends AnyFreeSpec with Matchers with ChiselSim { + +def expectedValuesFromAsm(path: String): Seq[BigInt] = { + val line = Source.fromFile(path).getLines().find(_.startsWith("# expected:")) + line match { + case Some(l) => + l.stripPrefix("# expected:") + .split(",") + .map(_.trim) + .filter(_.nonEmpty) + .map(str => BigInt(str, 16)) + .toSeq + case None => + Seq.empty + } +} + +def failTrace(msg: String = "Erreur",trace: ListBuffer[String]): Unit = { + val prefix = s"🛑 $msg\n" + val traceStr = + if (trace.nonEmpty) "Trace:\n" + trace.map(" - " + _).mkString("\n") + "\n" + else "" + fail(prefix + traceStr) +} + +def expectNextValue(dut: ZzTop, expected: BigInt, timeout: Int, trace: ListBuffer[String]): Unit = { + var cycles = 0 + dut.clock.step(12) // Attente d'un cycle proc (12 cycles systeme) + // Attendre une écriture dans x31 + while (!dut.io.valid_x31.get.peek().litToBoolean && cycles < timeout) { + dut.clock.step(12) // Attente d'un cycle proc + cycles += 1 + trace += f"Attente $cycles cycles" + } + + if (cycles < timeout){ + // Vérifier la nouvelle valeur + val got = dut.io.x31.get.peek().litValue + trace += f"Valeur attendue = 0x$expected%08X, reçue = 0x$got%08X" + if ( got != expected ) failTrace("Mauvaise sortie",trace) + } else { + trace += f"Valeur attendue = 0x$expected%08X, reçue = ⏰" + failTrace("Simulation bloquée",trace) + } +} + + + // Fonction de test commune + def testWith(file: String): Unit = { + val path = file.replace("/mem/","/").replace(".mem",".s") + if (Files.exists(Paths.get(path))){ + val expected = expectedValuesFromAsm(path) + simulate(new ZzTop(file,true)) { dut => + val trace = ListBuffer[String]() + expected.foreach { value => + expectNextValue(dut, value, timeout = 100,trace) + } + } + } + } + + val dir = sys.env("PROOT")+"/bench/mem/" + val suffix = ".mem" + val programFile = sys.env.get("PROG") + if (programFile.isDefined) { + val name = programFile.get + val path = dir+name+suffix + + "Simulation pour gtkwave" in { + simulate(new ZzTop(path,true)) { dut => + dut.io.switch.poke(8) // Paramétrage de l'affichage + dut.io.led.expect(dut.io.led.peek()) // test dummy + dut.clock.step(sys.env("CYCLES").toInt) + } + } + + s"Test unitaire $name" in testWith(path) + + } else { + // Récupération des fichiers .mem et génération de la table de programmes + val programs = Files.list(Paths.get(dir)).iterator().asScala.filter(_.toString.endsWith(suffix)) + val p2 = programs.map(path => (path.getFileName.toString.stripSuffix(suffix), path.toString)).toSeq + val table = Table(("name", "path"), p2: _*) + + // Déclaration des tests à partir de la table + forAll(table) { (name, path) => + s"Programme $name" in testWith(path) + } + } +}