Formats code

This commit is contained in:
2025-11-04 18:08:45 +01:00
parent fa60ce372f
commit 41c5da1a64
13 changed files with 664 additions and 593 deletions

5
.scalafmt.conf Normal file
View File

@@ -0,0 +1,5 @@
version = "3.7.15"
runner.dialect = scala213
indent.main = 4
indent.callSite = 4

View File

@@ -3,11 +3,12 @@ package common
import chisel3._
import _root_.circt.stage.ChiselStage
object EmitModule {
def main(args: Array[String]): Unit = {
if (args.length < 1) {
println("Usage: mill ... common.EmitModule <ModuleName> [constructor args...]")
println(
"Usage: mill ... common.EmitModule <ModuleName> [constructor args...]"
)
sys.exit(1)
}
@@ -25,7 +26,9 @@ object EmitModule {
Integer.valueOf(s.toInt)
}
// essai en Bool
else if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false")) {
else if (
s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false")
) {
java.lang.Boolean.valueOf(s.toBoolean)
}
// sinon, on laisse en String
@@ -45,7 +48,8 @@ object EmitModule {
ChiselStage.emitSystemVerilogFile(
gen = generator(),
firtoolOpts = Array("-disable-all-randomization", "-strip-debug-info"),
firtoolOpts =
Array("-disable-all-randomization", "-strip-debug-info"),
args = Array("--target-dir", s"RTL/$moduleName")
)
}

View File

@@ -11,9 +11,15 @@ import chisel3.util._
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
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 {
@@ -42,7 +48,7 @@ class BusInterconnect extends Module {
val LDIP_SIZE = 0x00000004L.U
val CLNT_BASE = 0x02000000L.U
val CLNT_SIZE = 0x0000C000L.U
val CLNT_SIZE = 0x0000c000L.U
val VMEM_BASE = 0x80000000L.U // Mémoire vidéo dual port
val VMEM_SIZE = 0x00020000L.U
@@ -78,7 +84,8 @@ class BusInterconnect extends Module {
val is_pclnt = RegNext(is_clnt)
val is_pvmem = RegNext(is_vmem)
io.master.rdata := MuxCase(0x0BADADDL.U(32.W),
io.master.rdata := MuxCase(
0x0badaddL.U(32.W),
Seq(
is_pdmem -> io.dmem.rdata,
is_pldip -> io.ldip.rdata,

View File

@@ -12,7 +12,7 @@ class CLint extends Module {
// Adresses des deux registres 64 bits du composant
val MTIMERCMP_ADDR = 0x4000
val MTIMER_ADDR = 0xBFF8
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))
@@ -35,20 +35,20 @@ class CLint extends Module {
io.bus.rdata := clintRegValue
when(io.bus.en === true.B) {
when (io.bus.be.asUInt.orR) { // Écriture
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) {
}.elsewhen(io.bus.addr === (MTIMERCMP_ADDR + 4).U) {
mtimecmp_hi := io.bus.wdata
}
} .otherwise { // Lecture
}.otherwise { // Lecture
when(io.bus.addr === MTIMER_ADDR.U) {
clintRegValue := mtime_lo
} .elsewhen(io.bus.addr === (MTIMER_ADDR + 4).U) {
}.elsewhen(io.bus.addr === (MTIMER_ADDR + 4).U) {
clintRegValue := mtime_hi
} .elsewhen(io.bus.addr === MTIMERCMP_ADDR.U) {
}.elsewhen(io.bus.addr === MTIMERCMP_ADDR.U) {
clintRegValue := mtimecmp_lo
} .elsewhen(io.bus.addr === (MTIMERCMP_ADDR + 4).U) {
}.elsewhen(io.bus.addr === (MTIMERCMP_ADDR + 4).U) {
clintRegValue := mtimecmp_hi
}
}

View File

@@ -44,9 +44,9 @@ class ClockGen(val divBy: Int = 10, useSim: Boolean = false) extends Module {
io.locked := !rstReg
when (cntReg === midCnt) {
when(cntReg === midCnt) {
outReg := true.B
} .elsewhen (cntReg === maxCnt) {
}.elsewhen(cntReg === maxCnt) {
outReg := false.B
cntReg := 0.U
rstReg := false.B

View File

@@ -4,10 +4,12 @@ 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.
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 {
) 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.
@@ -15,14 +17,18 @@ class Compteur(
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 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.
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

View File

@@ -11,29 +11,44 @@ class GeneSync extends Module {
})
// Paramètres de synchronisation horizontale et verticale
val synchroX = VecInit(Seq(
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(
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)
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 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

View File

@@ -13,15 +13,23 @@ class dpram( // nom identique à celui du verilog
val ADDR_WIDTH: Int,
val DATA_WIDTH: Int,
val INIT_FILE: String = "program.mem"
) extends BlackBox(Map(
) 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 {
"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.")
require(
DATA_WIDTH == NUM_COL * COL_WIDTH,
"DATA_WIDTH must be equal to NUM_COL*COL_WIDTH."
)
addResource("/vsrc/dpram.v")
@@ -46,7 +54,8 @@ class dpram( // nom identique à celui du verilog
class xpm_memory_tdpram( // nom identique à celui de Xilinx
val ADDR_WIDTH: Int,
val INIT_FILE: String
) extends BlackBox(Map(
) extends BlackBox(
Map(
"ADDR_WIDTH_A" -> IntParam(ADDR_WIDTH),
"ADDR_WIDTH_B" -> IntParam(ADDR_WIDTH),
"BYTE_WRITE_WIDTH_A" -> 8,
@@ -59,7 +68,9 @@ class xpm_memory_tdpram( // nom identique à celui de Xilinx
"READ_LATENCY_B" -> 1,
"WRITE_DATA_WIDTH_A" -> 32,
"WRITE_DATA_WIDTH_B" -> 32
)) with HasBlackBoxResource {
)
)
with HasBlackBoxResource {
val io = IO(new Bundle {
// Port A
val clka = Input(Clock())
@@ -99,7 +110,7 @@ class xpm_memory_tdpram( // nom identique à celui de Xilinx
* pour faciliter le préchargement (on espère).
*
\*/
class IDMem(sim: Boolean, addrWidth: Int, memFile: String ="") extends Module {
class IDMem(sim: Boolean, addrWidth: Int, memFile: String = "") extends Module {
val io = IO(new Bundle {
val clki = Input(Clock())
val ibus = new BusInterface
@@ -113,13 +124,15 @@ class IDMem(sim: Boolean, addrWidth: Int, memFile: String ="") extends Module {
// 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(
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
@@ -136,7 +149,12 @@ class IDMem(sim: Boolean, addrWidth: Int, memFile: String ="") extends Module {
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))
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

View File

@@ -23,10 +23,10 @@ class LedIp extends Module {
// 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
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
}.otherwise { // Lecture des boutons poussoirs, pas de débounce car lus par le soft
btnReg := Fill(29, 0.U) ## io.button
}
}
@@ -39,8 +39,7 @@ class LedIp extends Module {
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 =>
io.led := MuxLookup(io.switch(2, 0), 0.U(4.W))((0 until 8).map { i =>
(i.U -> data((i * 4) + 3, i * 4))
})
}

View File

@@ -39,18 +39,17 @@ class Rv32i(sim: Boolean = true) extends Module {
// Debug instruction
io.x31 := reg_file.io.rs1_data;
if(sim)
{
if (sim) {
io.valid_x31.get := true.B;
}
io.dbus.addr := 0.U;
io.dbus.wdata := 0.U;
io.dbus.be := VecInit(false.B,false.B,false.B,false.B)
io.dbus.be := VecInit(false.B, false.B, false.B, false.B)
io.dbus.en := false.B;
io.ibus.wdata := 0.U;
io.ibus.be := VecInit(false.B,false.B,false.B,false.B)
io.ibus.be := VecInit(false.B, false.B, false.B, false.B)
//locally(sim)
// locally(sim)
}

View File

@@ -28,13 +28,15 @@ class VgaIp extends Module {
// 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 =>
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)
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)
}
}

View File

@@ -2,7 +2,7 @@ package projet
import chisel3._
class ZzTop(file: String ="",sim: Boolean = true) extends Module {
class ZzTop(file: String = "", sim: Boolean = true) extends Module {
val io = IO(new Bundle {
// Interface debug minimale
val switch = Input(UInt(4.W))
@@ -27,16 +27,17 @@ class ZzTop(file: String ="",sim: Boolean = true) extends Module {
val rst = (reset.asBool || !clkg.io.locked).asAsyncReset
// Ces deux IPs doivent tourner à 125 MHz
val vmem = Module(new IDMem(sim,17))
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) {
// Expose core for testing usage
val core = 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 dmem = Module(new IDMem(sim, 16, file))
val ldip = Module(new LedIp)
val clnt = Module(new CLint)
@@ -48,12 +49,11 @@ class ZzTop(file: String ="",sim: Boolean = true) extends Module {
ldip.io.x31 := core.io.x31
io.led := ldip.io.led
if (sim){
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
@@ -70,5 +70,7 @@ class ZzTop(file: String ="",sim: Boolean = true) extends Module {
io.r := vgad.io.r
io.g := vgad.io.g
io.b := vgad.io.b
core
}
}

View File

@@ -11,8 +11,9 @@ 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:"))
def expectedValuesFromAsm(path: String): Seq[BigInt] = {
val line =
Source.fromFile(path).getLines().find(_.startsWith("# expected:"))
line match {
case Some(l) =>
l.stripPrefix("# expected:")
@@ -24,17 +25,23 @@ def expectedValuesFromAsm(path: String): Seq[BigInt] = {
case None =>
Seq.empty
}
}
}
def failTrace(msg: String = "Erreur",trace: ListBuffer[String]): Unit = {
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"
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 = {
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
@@ -44,41 +51,40 @@ def expectNextValue(dut: ZzTop, expected: BigInt, timeout: Int, trace: ListBuffe
trace += f"Attente $cycles cycles"
}
if (cycles < timeout){
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)
if (got != expected) failTrace("Mauvaise sortie", trace)
} else {
trace += f"Valeur attendue = 0x$expected%08X, reçue = ⏰"
failTrace("Simulation bloquée",trace)
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 path = file.replace("/mem/", "/").replace(".mem", ".s")
if (Files.exists(Paths.get(path))) {
val expected = expectedValuesFromAsm(path)
simulate(new ZzTop(file,true)) { dut =>
simulate(new ZzTop(file, true)) { dut =>
val trace = ListBuffer[String]()
expected.foreach { value =>
expectNextValue(dut, value, timeout = 100,trace)
expectNextValue(dut, value, timeout = 100, trace)
}
}
}
}
val dir = sys.env("PROOT")+"/bench/mem/"
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
val path = dir + name + suffix
"Simulation pour gtkwave" in {
simulate(new ZzTop(path,true)) { dut =>
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)
@@ -89,8 +95,16 @@ def expectNextValue(dut: ZzTop, expected: BigInt, timeout: Int, trace: ListBuffe
} 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 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