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,50 +3,54 @@ 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...]")
sys.exit(1)
def main(args: Array[String]): Unit = {
if (args.length < 1) {
println(
"Usage: mill ... common.EmitModule <ModuleName> [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")
)
}
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")
)
}
}

View File

@@ -9,81 +9,88 @@ import chisel3.util._
// 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
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)
})
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 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")
// 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 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 LDIP_BASE = 0x30000000L.U
val LDIP_SIZE = 0x00000004L.U
val CLNT_BASE = 0x02000000L.U
val CLNT_SIZE = 0x0000C000L.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 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)
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 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)
// 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
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
)
)
)
}

View File

@@ -5,52 +5,52 @@ 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())
})
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
// 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))
// 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)
// 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))
// 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
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
}
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
}
}
}
}
}

View File

@@ -9,56 +9,56 @@ import chisel3.util._
* 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 {
class clk_wiz extends BlackBox with HasBlackBoxResource {
addResource("/vsrc/clk_wiz.v")
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())
})
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 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
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)
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)
rstReg := Mux(io.reset.asBool, true.B, rstReg)
io.locked := !rstReg
io.locked := !rstReg
when (cntReg === midCnt) {
outReg := true.B
} .elsewhen (cntReg === maxCnt) {
outReg := false.B
cntReg := 0.U
rstReg := false.B
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
}
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
}
}

View File

@@ -3,26 +3,32 @@ 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 {
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 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.
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,31 +11,46 @@ class GeneSync extends Module {
})
// 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 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
))
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
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
@@ -53,9 +68,9 @@ class GeneSync extends Module {
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
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

View File

@@ -13,82 +13,93 @@ class dpram( // nom identique à celui du verilog
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 {
) 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.")
require(
DATA_WIDTH == NUM_COL * COL_WIDTH,
"DATA_WIDTH must be equal to NUM_COL*COL_WIDTH."
)
addResource("/vsrc/dpram.v")
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))
})
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))
) 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))
// 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())
})
val sleep = Input(Bool())
})
}
/*\
* Attention, les BRAM sont adressables en mots, et les adresses émises
@@ -99,66 +110,73 @@ 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 {
val io = IO(new Bundle {
val clki = Input(Clock())
val ibus = new BusInterface
val clkd = Input(Clock())
val dbus = new BusInterface
})
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
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
))
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
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.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
}
dpram.io.sleep := false.B
}
}

View File

@@ -6,41 +6,40 @@ 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
})
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 !
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))
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
// 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
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 é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))
})
// 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))
})
}

View File

@@ -3,54 +3,53 @@ 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
})
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
})
val reg_pc = RegInit(0.U(32.W));
val reg_file = Module(new RegFile(sim));
reg_file.io.rd_data := 0.U;
reg_file.io.we := 0.U;
val reg_pc = RegInit(0.U(32.W));
val reg_file = Module(new RegFile(sim));
reg_file.io.rd_data := 0.U;
reg_file.io.we := 0.U;
// Increment PC each clock
reg_pc := reg_pc + 4.U;
// Increment PC each clock
reg_pc := reg_pc + 4.U;
// Fetch instruction
io.ibus.en := true.B;
io.ibus.addr := reg_pc;
// Fetch instruction
io.ibus.en := true.B;
io.ibus.addr := reg_pc;
val insn = io.ibus.rdata;
val insn = io.ibus.rdata;
// Decode rs1 index
val rs1 = insn(19, 15)
reg_file.io.rs1_addr := rs1
// Decode rs2 index
val rs2 = insn(24, 20)
reg_file.io.rs2_addr := rs2
// Decode rd index
val rd = insn(11, 7)
reg_file.io.rd_addr := rd
// Decode rs1 index
val rs1 = insn(19, 15)
reg_file.io.rs1_addr := rs1
// Decode rs2 index
val rs2 = insn(24, 20)
reg_file.io.rs2_addr := rs2
// Decode rd index
val rd = insn(11, 7)
reg_file.io.rd_addr := rd
// Decode imm-u
val imm_u = insn(31, 12)
// Decode imm-u
val imm_u = insn(31, 12)
// Debug instruction
io.x31 := reg_file.io.rs1_data;
if(sim)
{
io.valid_x31.get := true.B;
}
// Debug instruction
io.x31 := reg_file.io.rs1_data;
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.en := false.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.en := false.B;
io.ibus.wdata := 0.U;
io.ibus.be := VecInit(false.B,false.B,false.B,false.B)
io.ibus.wdata := 0.U;
io.ibus.be := VecInit(false.B, false.B, false.B, false.B)
//locally(sim)
// locally(sim)
}

View File

@@ -4,37 +4,39 @@ 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))
})
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))
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)
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))
})
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,73 +2,75 @@ 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
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))
})
// 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
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
// 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)
// 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 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 //
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
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
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
core
}
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
}
}

View File

@@ -11,91 +11,105 @@ 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)
}
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
}
}
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)
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)
}
}
}
}