lab4 tutorial updated and imported some docs from orginal YatCPU docs

This commit is contained in:
PurplePower
2025-08-25 22:03:10 +08:00
parent bd3a38a3c1
commit aaa87c1fdf
121 changed files with 2368 additions and 163 deletions

View File

@@ -3,8 +3,15 @@ cmake_minimum_required(VERSION 3.18)
project(yatcpu-programs C CXX ASM)
# Setting variables
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 --target=riscv32-unknown-elf -march=rv32i -mabi=ilp32")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -O0 --target=riscv32-unknown-elf -march=rv32i -mabi=ilp32")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -march=rv32i -mabi=ilp32")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -O0 -march=rv32i -mabi=ilp32")
if (CMAKE_C_COMPILER EQUAL "clang")
# append --target=riscv32-unknown-elf to flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --target=riscv32-unknown-elf")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} --target=riscv32-unknown-elf")
endif()
set(C_PROGRAMS tetris hello fibonacci quicksort paging tetris_mmu say_goodbye)
set(ASM_PROGRAMS mmio sb)
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/link.lds)
@@ -39,5 +46,10 @@ foreach(program IN LISTS C_PROGRAMS ASM_PROGRAMS PROGRAMS)
POST_BUILD
COMMAND ${CMAKE_OBJCOPY} ARGS ${OBJCOPY_ARGS} $<TARGET_FILE:${program}> ${CMAKE_SOURCE_DIR}/${DEST_DIR}/${program}.asmbin
)
add_custom_command(
TARGET ${program}
POST_BUILD
COMMAND ${CMAKE_OBJDUMP} ARGS -d $<TARGET_FILE:${program}> > ${CMAKE_SOURCE_DIR}/${DEST_DIR}/${program}.dump
)
endforeach()

View File

@@ -1,3 +0,0 @@
rmdir /Q /S build
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -G"NMake Makefiles" -B build .
cmake --build build

3
lab4/csrc/build.cl.bat Normal file
View File

@@ -0,0 +1,3 @@
rmdir /Q /S build
cmake -DCMAKE_TOOLCHAIN_FILE="./toolchain.cl.cmake" -G"NMake Makefiles" -B build .
cmake --build build

3
lab4/csrc/build.cl.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
rm -rf build
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cl.cmake -B build . && cmake --build build --parallel `nproc`

3
lab4/csrc/build.gnu.bat Normal file
View File

@@ -0,0 +1,3 @@
rmdir /Q /S build
cmake -DCMAKE_TOOLCHAIN_FILE="./toolchain.riscv-gnu.cmake" -G"Unix Makefiles" -B build .
cmake --build build

3
lab4/csrc/build.gnu.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
rm -rf build
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.riscv-gnu.cmake -B build . && cmake --build build --parallel `nproc`

View File

@@ -1,3 +0,0 @@
#!/bin/sh
rm -rf build
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -B build . && cmake --build build --parallel `nproc`

View File

@@ -19,4 +19,6 @@ int fib(int a) {
int main() {
*(int *)(4) = fib(10);
*(int*)(0) = 0xbabecafe; // anchor for program finished
return 0;
}

View File

@@ -15,30 +15,31 @@ void waste_some_time(int cycle) {
int main() {
// const char* s = "abcd";
const char* s = "Never gonna give you up~ Never gonna let you down~\n"
"Never gonna run around and~ desert you~\n";
// const char* s = "abcd";
const char* s = "Never gonna give you up~ Never gonna let you down~\n"
"Never gonna run around and~ desert you~\n";
while (1) {
while (1) {
// for (int i = 0; i < ; i++) {
// uart_send_char(s[i]);
// waste_some_time(100);
// }
const char *p = s;
while (*p != 0)
{
uart_send_char(*p);
p++;
waste_some_time(500);
}
// for (int i = 0; i < ; i++) {
// uart_send_char(s[i]);
// waste_some_time(100);
// }
const char *p = s;
while (*p != 0)
{
uart_send_char(*p);
p++;
waste_some_time(500);
break; // print once, but pressing CPU reset can print again
}
return 0;
waste_some_time(500);
break; // print once, but pressing CPU reset can print again
}
*(int*)(0) = 0xbabecafe; // anchor for program finished
return 0;
}

View File

@@ -11,6 +11,7 @@ set(CMAKE_ASM_COMPILER clang)
set(CMAKE_ASM_COMPILER_TARGET ${triple})
set(CMAKE_AR llvm-ar CACHE FILEPATH "Archiver")
set(CMAKE_OBJCOPY llvm-objcopy)
set(CMAKE_OBJDUMP llvm-objdump)
set(CMAKE_C_FLAGS_INIT "-mno-relax")
set(CMAKE_CXX_FLAGS_INIT "-mno-relax")

View File

@@ -0,0 +1,21 @@
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR riscv32)
set(triple riscv32-unknown-elf)
set(CMAKE_C_COMPILER riscv64-unknown-elf-gcc)
set(CMAKE_C_COMPILER_TARGET ${triple})
set(CMAKE_CXX_COMPILER riscv64-unknown-elf-g++)
set(CMAKE_CXX_COMPILER_TARGET ${triple})
set(CMAKE_ASM_COMPILER riscv64-unknown-elf-gcc)
set(CMAKE_ASM_COMPILER_TARGET ${triple})
# set(CMAKE_AR llvm-ar CACHE FILEPATH "Archiver")
set(CMAKE_OBJCOPY riscv64-unknown-elf-objcopy)
set(CMAKE_OBJDUMP riscv64-unknown-elf-objdump)
# set(CMAKE_C_FLAGS_INIT "-mno-relax")
# set(CMAKE_CXX_FLAGS_INIT "-mno-relax")
# set(CMAKE_ASM_FLAGS_INIT "-mno-relax")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-nostdlib -static ")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-nostdlib -static")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-nostdlib -static")

View File

@@ -91,26 +91,26 @@ class AXI4LiteChannels(addrWidth: Int, dataWidth: Int) extends Bundle {
}
class AXI4LiteSlaveBundle(addrWidth: Int, dataWidth: Int) extends Bundle {
val read = Output(Bool())
val write = Output(Bool())
val read_data = Input(UInt(dataWidth.W))
val read_valid = Input(Bool())
val read = Output(Bool()) // tell slave device to read
val write = Output(Bool()) // tell slave device to write
val read_data = Input(UInt(dataWidth.W)) // data read from slave device
val read_valid = Input(Bool()) // indicates if read_data is valid
val write_data = Output(UInt(dataWidth.W))
val write_strobe = Output(Vec(Parameters.WordSize, Bool()))
val address = Output(UInt(addrWidth.W))
}
class AXI4LiteMasterBundle(addrWidth: Int, dataWidth: Int) extends Bundle {
val read = Input(Bool())
val write = Input(Bool())
val read = Input(Bool()) // request a read transaction
val write = Input(Bool()) // request a write transaction
val read_data = Output(UInt(dataWidth.W))
val write_data = Input(UInt(dataWidth.W))
val write_strobe = Input(Vec(Parameters.WordSize, Bool()))
val address = Input(UInt(addrWidth.W))
val busy = Output(Bool())
val read_valid = Output(Bool())
val write_valid = Output(Bool())
val busy = Output(Bool()) // if busy, master is not ready to accept new transactions
val read_valid = Output(Bool()) // indicates read transaction done successfully and asserts for ONLY 1 cycle.
val write_valid = Output(Bool()) // indicates write transaction done successfully and asserts for ONLY 1 cycle.
}
object AXI4LiteStates extends ChiselEnum {

View File

@@ -23,11 +23,17 @@ class DummyMaster extends Module {
val io = IO(new Bundle {
val channels = new AXI4LiteChannels(Parameters.AddrBits, Parameters.DataBits)
})
val master = Module(new AXI4LiteMaster(Parameters.AddrBits, Parameters.DataBits))
master.io.channels <> io.channels
master.io.bundle.write_strobe := VecInit(Seq.fill(Parameters.WordSize)(false.B))
master.io.bundle.write_data := 0.U
master.io.bundle.write := false.B
master.io.bundle.read := false.B
master.io.bundle.address := 0.U
// NOTE: not using AXI4LiteMaster to save resources
io.channels.read_address_channel.ARVALID := false.B
io.channels.read_address_channel.ARADDR := 0.U
io.channels.read_address_channel.ARPROT := 0.U
io.channels.read_data_channel.RREADY := false.B
io.channels.write_address_channel.AWVALID := false.B
io.channels.write_address_channel.AWADDR := 0.U
io.channels.write_address_channel.AWPROT := 0.U
io.channels.write_data_channel.WVALID := false.B
io.channels.write_data_channel.WDATA := 0.U
io.channels.write_data_channel.WSTRB := 0.U
io.channels.write_response_channel.BREADY := false.B
}

View File

@@ -18,15 +18,19 @@ import bus.{AXI4LiteChannels, AXI4LiteSlave}
import chisel3._
import riscv.Parameters
// A dummy AXI4 slave that only returns 0 on read
// and ignores all writes
// A dummy AXI4 slave that doesn't respond to anything
class DummySlave extends Module {
val io = IO(new Bundle {
val channels = Flipped(new AXI4LiteChannels(4, Parameters.DataBits))
})
val slave = Module(new AXI4LiteSlave(Parameters.AddrBits, Parameters.DataBits))
slave.io.channels <> io.channels
slave.io.bundle.read_valid := true.B
slave.io.bundle.read_data := 0xDEADBEEFL.U
io.channels.read_address_channel.ARREADY := false.B
io.channels.read_data_channel.RVALID := false.B
io.channels.read_data_channel.RDATA := 0.U
io.channels.read_data_channel.RRESP := 0.U
io.channels.write_address_channel.AWREADY := false.B
io.channels.write_data_channel.WREADY := false.B
io.channels.write_response_channel.BVALID := false.B
io.channels.write_response_channel.BRESP := 0.U
}

View File

@@ -14,11 +14,43 @@
package riscv
import bus.{AXI4LiteMaster, AXI4LiteMasterBundle, AXI4LiteSlave, AXI4LiteSlaveBundle}
import scala.util.Random
import scala.util.control.Breaks._
import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
import peripheral.{Memory, ROMLoader}
import peripheral.{Memory, ROMLoader, DummyMaster, DummySlave}
import bus.{AXI4LiteMaster, AXI4LiteMasterBundle, AXI4LiteSlave, AXI4LiteSlaveBundle}
// =======================================
// Common Functions for Flexible tests
// =======================================
object FlexibleTestHelper {
def init_write_transaction(bundle:AXI4LiteMasterBundle, clk: Clock, address: UInt, data: UInt, strobe: UInt) = {
bundle.read.poke(false.B)
bundle.write.poke(true.B)
bundle.address.poke(address)
bundle.write_data.poke(data)
for (i <- 0 until 4) bundle.write_strobe(i).poke(strobe(i))
clk.step() // this cycle is not counted into transaction
bundle.write.poke(false.B)
bundle.address.poke(0.U)
bundle.write_data.poke(0.U)
}
def init_read_transaction(bundle:AXI4LiteMasterBundle, clk: Clock, address: UInt) = {
bundle.read.poke(true.B)
bundle.write.poke(false.B)
bundle.address.poke(address)
clk.step() // this cycle is not counted into transaction
bundle.read.poke(false.B)
bundle.address.poke(0.U)
}
}
class TimerTest extends AnyFlatSpec with ChiselScalatestTester {
class TestTimerLimit extends Module {
@@ -35,34 +67,54 @@ class TimerTest extends AnyFlatSpec with ChiselScalatestTester {
behavior of "Timer"
it should "read and write the limit" in {
test(new TestTimerLimit).withAnnotations(TestAnnotations.annos) {
c =>
c.io.bundle.read.poke(false.B)
c.io.bundle.write.poke(true.B)
c.io.bundle.address.poke(0x4.U)
c.io.bundle.write_data.poke(0x990315.U)
c.clock.step()
test(new TestTimerLimit).withAnnotations(TestAnnotations.annos) { c =>
var (write_success, read_success) = (false, false)
// -----------------------------
// initiate a write transaction to timer limit
FlexibleTestHelper.init_write_transaction(c.io.bundle, c.clock, 0x4.U, 0x990315.U, 0xF.U)
// check correct write
breakable {for (i <- 1 until 20) {
if (c.io.bundle.write_valid.peekBoolean()) {
c.io.limit.expect(0x990315.U)
write_success = true
break()
}
c.io.bundle.busy.expect(true.B)
c.io.bundle.write.poke(false.B)
c.io.bundle.address.poke(0x0.U)
c.io.bundle.write_data.poke(0.U)
c.clock.step(8)
c.io.bundle.busy.expect(false.B)
c.io.bundle.write_valid.expect(true.B)
c.io.limit.expect(0x990315.U)
c.io.bundle.read.poke(true.B)
c.io.bundle.address.poke(0x4.U)
c.clock.step()
}}
c.clock.step(2)
c.io.bundle.busy.expect(false.B) // master should be available soon after transaction
if (!write_success) throw new Exception("Timer write test failed")
// -----------------------------
// initiate a read transaction to timer limit
FlexibleTestHelper.init_read_transaction(c.io.bundle, c.clock, 0x4.U)
// check correct read
breakable { for (i <- 1 until 20) {
if (c.io.bundle.read_valid.peekBoolean()) {
c.io.bundle.read_data.expect(0x990315.U)
read_success = true
break()
}
c.io.bundle.busy.expect(true.B)
c.clock.step(6)
c.io.bundle.busy.expect(false.B)
c.io.bundle.read_valid.expect(true.B)
c.io.bundle.read_data.expect(0x990315.U)
c.clock.step()
}}
c.clock.step(2)
c.io.bundle.busy.expect(false.B) // master should be available soon after transaction
if (!read_success) throw new Exception("Timer read test failed")
println("Timer test passed")
}
}
}
class MemoryTest extends AnyFlatSpec with ChiselScalatestTester {
class MemoryTestF extends AnyFlatSpec with ChiselScalatestTester {
class MemoryTest extends Module {
val io = IO(new Bundle {
val bundle = new AXI4LiteMasterBundle(Parameters.AddrBits, Parameters.DataBits)
@@ -75,90 +127,397 @@ class MemoryTest extends AnyFlatSpec with ChiselScalatestTester {
master.io.bundle <> io.bundle
master.io.bundle.write_strobe := VecInit(io.write_strobe.asBools)
master.io.channels <> memory.io.channels
memory.io.debug_read_address := 0.U
}
behavior of "Memory"
it should "perform read and write" in {
test(new MemoryTest).withAnnotations(TestAnnotations.annos) { c =>
c.io.bundle.read.poke(false.B)
c.io.bundle.write.poke(true.B)
c.io.write_strobe.poke(0xF.U)
c.io.bundle.address.poke(0x4.U)
c.io.bundle.write_data.poke(0xDEADBEEFL.U)
var (write_success, read_success) = (false, false)
// -------------------------
// initiate write transaction
FlexibleTestHelper.init_write_transaction(c.io.bundle, c.clock, 0x4.U, 0xDEADBEEFL.U, 0xF.U)
breakable { for (i <- 1 until 20) {
if (c.io.bundle.write_valid.peekBoolean()) {
write_success = true
break()
}
c.io.bundle.busy.expect(true.B)
c.clock.step()
}}
c.clock.step(2)
c.io.bundle.busy.expect(false.B) // master should be available soon after transaction
if (!write_success) throw new Exception("Memory write test failed")
// ------------------------
// initialte read transaction
FlexibleTestHelper.init_read_transaction(c.io.bundle, c.clock, 0x4.U)
breakable { for (i <- 1 until 20) {
if (c.io.bundle.read_valid.peekBoolean()) {
c.io.bundle.read_data.expect(0xDEADBEEFL.U)
read_success = true
break()
}
c.io.bundle.busy.expect(true.B)
c.clock.step()
}}
c.clock.step()
c.io.bundle.busy.expect(true.B)
c.io.bundle.write.poke(false.B)
c.io.bundle.address.poke(0x0.U)
c.io.bundle.write_data.poke(0.U)
c.clock.step(8)
c.io.bundle.busy.expect(false.B)
c.io.bundle.write_valid.expect(true.B)
c.io.bundle.read.poke(true.B)
c.io.bundle.address.poke(0x4.U)
c.clock.step()
c.io.bundle.busy.expect(true.B)
c.clock.step(6)
c.io.bundle.busy.expect(false.B)
c.io.bundle.read_valid.expect(true.B)
c.io.bundle.read_data.expect(0xDEADBEEFL.U)
if (!read_success) throw new Exception("Memory read test failed")
println("Memory test passed")
}
}
}
class ROMLoaderTest extends AnyFlatSpec with ChiselScalatestTester {
class ROMLoaderTestF extends AnyFlatSpec with ChiselScalatestTester {
/*
┌──────────────────────────────────┐
│ ROMLoader │ ┌───────────┐
│ │ │ Memory │
┌──────────────────┐ ├───────────┐ ┌───────┤ │ │
│ ◄─────┼─rom_addr │ │ │ AXI ├────────┐ │
│ Instruction ROM │ │ │ │ master┼─────► slave │ │
│ ┼─────┼►rom_data ├◄────────────►│ │ │ │ │
└──────────────────┘ ├───────────┘ └───────┤ ├──┬─────┘ │
│ │ │ │ │
│ │ └──┼────────┘
└──────────────────────────────────┘ │
ROM contents
*/
class ROMLoaderTest extends Module {
val io = IO(new Bundle {
val rom_address = Output(UInt(32.W))
val rom_data = Input(UInt(32.W))
val load_start = Input(Bool())
val load_address = Input(UInt(32.W))
val load_finished = Output(Bool())
val bundle = new AXI4LiteSlaveBundle(32, 32)
})
val slave_bundle = new AXI4LiteSlaveBundle(32, 32)
val rom_loader = Module(new ROMLoader(2))
rom_loader.io.rom_data := io.rom_data
val init_address = Input(UInt(32.W))
val init_data = Input(UInt(32.W))
val init_enable = Input(Bool())
})
val rom = SyncReadMem(32, UInt(Parameters.DataBits.W))
val rom_loader = Module(new ROMLoader(rom.length.toInt))
rom_loader.io.rom_data := rom.read(rom_loader.io.rom_address, true.B)
rom_loader.io.load_start := io.load_start
rom_loader.io.load_address := io.load_address
io.load_finished := rom_loader.io.load_finished
io.rom_address := rom_loader.io.rom_address
val slave = Module(new AXI4LiteSlave(Parameters.AddrBits, Parameters.DataBits))
slave.io.bundle <> io.bundle
slave.io.bundle <> io.slave_bundle
slave.io.channels <> rom_loader.io.channels
slave.io.bundle.read_data := 0.U
when(io.init_enable) {
rom.write(io.init_address, io.init_data)
}
}
/*
ROMLoader works as a loader to move program from ROM to RAM.
*/
behavior of "ROMLoader"
it should "load program" in {
it should "load program through AXI to mem" in {
test(new ROMLoaderTest).withAnnotations(TestAnnotations.annos) { c =>
c.io.load_address.poke(0x100.U)
// ----------------------------
// init ROM with some data
c.io.init_enable.poke(true.B)
c.io.init_address.poke(0.U)
c.io.init_data.poke(0.U)
c.clock.step()
for (i <- 0 until c.rom.length.toInt) {
val data = ((i + 1997) * 23753).U
// val data = (i + 1).U
c.io.init_address.poke(i.U)
c.io.init_data.poke(data)
c.clock.step()
}
c.io.init_enable.poke(false.B)
c.clock.step(2)
// --------------------------------
// read from ROMLoader and check the data
c.io.load_address.poke(0x0.U)
c.io.load_start.poke(true.B)
c.clock.step()
c.io.load_start.poke(false.B)
c.io.rom_address.expect(0x0.U)
c.clock.step(8)
c.io.bundle.write.expect(true.B)
c.io.bundle.address.expect(0x100.U)
c.clock.step(4)
c.io.rom_address.expect(0x1.U)
c.clock.step(7)
c.io.rom_address.expect(0x1.U)
c.io.bundle.write.expect(true.B)
c.io.bundle.address.expect(0x104.U)
c.clock.step()
c.io.rom_address.expect(0x1.U)
c.clock.step(3)
for (i <- 0 until c.rom.length.toInt) {
var word_write_done = false
breakable { for (j <- 0 until 20) {
while (c.io.slave_bundle.write.peekBoolean()) { // loop until `write` is set to low for this word
c.io.slave_bundle.address.expect((i*4).U)
c.io.slave_bundle.write_data.expect(((i + 1997) * 23753).U)
word_write_done = true
c.clock.step()
}
if (word_write_done) break()
c.clock.step()
}}
if (!word_write_done) throw new Exception(s"Write for word $i failed after 20 cycles")
}
c.clock.step(2)
c.io.load_finished.expect(true.B)
c.io.slave_bundle.write.expect(false.B)
println(s"Successfully tested with ROM of ${c.rom.length.toInt} words")
}
}
}
class FunctionalTest extends AnyFlatSpec with ChiselScalatestTester {
class TestBox extends Module {
val io = IO(new Bundle {
val master = new AXI4LiteMasterBundle(Parameters.AddrBits, Parameters.DataBits)
val slave = new AXI4LiteSlaveBundle(Parameters.AddrBits, Parameters.DataBits)
val readout_data = Input(UInt(Parameters.DataBits.W))
val detach = Input(Bool()) // detach slave and master connection, used for simulating busy
})
val master = Module(new AXI4LiteMaster(Parameters.AddrBits, Parameters.DataBits))
val slave = Module(new AXI4LiteSlave(Parameters.AddrBits, Parameters.DataBits))
val dm = Module(new DummyMaster)
val ds = Module(new DummySlave)
master.io.bundle <> io.master
slave.io.bundle <> io.slave
when (io.detach) {
master.io.channels <> ds.io.channels
slave.io.channels <> dm.io.channels
}
.otherwise {
master.io.channels <> slave.io.channels
dm.io.channels <> ds.io.channels
}
// slave response is put here since chiseltest will take value poked just now for `expect` and `peek`,
// which may be quite different from wave form.
when (io.slave.read) { // slave device responds to read request
slave.io.bundle.read_data := io.readout_data
slave.io.bundle.read_valid := true.B
}
}
// this test also benchmarks cycles of write transaction
behavior of "Write Function"
it should "write data with correct response" in {
test(new TestBox).withAnnotations(TestAnnotations.annos) { c =>
var address = 0x4.U
var data = 0xDEADBEEFL.U
val strobe = 0xE.U(Parameters.WordSize.W)
var cycle_passed = 0
var cycle_arrive_slave = -1
var cycle_write = -1
var cycle_next = -1 // cycles taken that master is ready for next transaction
// ------------------------
// start write transaction
FlexibleTestHelper.init_write_transaction(c.io.master, c.clock, address, data, strobe)
// ------------------------
// check correct write at slave
breakable { while (true) {
if (c.io.slave.write.peekBoolean()) { // check when `write` asserted
c.io.slave.address.expect(address)
c.io.slave.write_data.expect(data)
for (i <- 0 until 4) c.io.slave.write_strobe(i).expect(strobe(i))
cycle_arrive_slave = cycle_passed
}
if (c.io.master.write_valid.peekBoolean()) {
// master knows write succeeded, make sure `write_valid` is high for only 1 cycle
if (cycle_write == -1) cycle_write = cycle_passed
else throw new Exception("Write test failed: write_valid is high for more than 1 cycle")
}
if (!c.io.master.busy.peekBoolean()) {
cycle_next = cycle_passed
break()
}
if (cycle_passed > 20) {
throw new Exception("Write test failed: transaction seems never ends after 20 cycles")
}
c.io.master.busy.expect(true.B)
c.clock.step()
cycle_passed += 1
}}
c.clock.step()
if (cycle_arrive_slave == -1) {
throw new Exception("Write test failed: no data arrives slave")
}
if (cycle_write == -1) {
throw new Exception("Write test failed: no write response arrives master")
}
println(s"Write transaction cost ${cycle_write} cycles, ${cycle_next} cycles for next transaction")
}
}
behavior of "Read Function"
it should "read correct data" in {
test(new TestBox).withAnnotations(TestAnnotations.annos) { c =>
var address = 0x4.U
var data = 0xDEADBEEFL.U
var cycle_passed = 0
var cycle_arrive_slave = -1
var cycle_read = -1 // read out data arrives at master
var cycle_next = -1 // cycles taken that master is ready for next transaction
// ----------------------
// start read transaction
FlexibleTestHelper.init_read_transaction(c.io.master, c.clock, address)
// -----------------------
// check correct read at slave
breakable {while (true) {
if (c.io.slave.read.peekBoolean()) { // check when `read` asserted
c.io.slave.address.expect(address)
cycle_arrive_slave = cycle_passed
}
if (c.io.master.read_valid.peekBoolean()) {
// master knows read succeeded, make sure `read_valid` is high for only 1 cycle
if (cycle_read == -1) cycle_read = cycle_passed
else throw new Exception("Read test failed: read_valid is high for more than 1 cycle")
}
if (!c.io.master.busy.peekBoolean()) {
cycle_next = cycle_passed
break()
}
if (cycle_passed > 20) {
throw new Exception("Read test failed: transaction seems never ends after 20 cycles")
}
c.io.master.busy.expect(true.B)
c.clock.step()
cycle_passed += 1
}}
c.clock.step()
if (cycle_arrive_slave == -1) {
throw new Exception("Read test failed: no data arrives slave")
}
if (cycle_read == -1) {
throw new Exception("Read test failed: no read data arrives master")
}
println(s"Read transaction cost ${cycle_read} cycles for data read, ${cycle_next} cycles for next transaction")
}
}
behavior of "Bus"
it should "handle continuous transactions" in {
test(new TestBox).withAnnotations(TestAnnotations.annos) { c =>
val num_transactions = 1000
var num_success_transactions = 0
var total_cycles = 0
var slave_trans_to_expect = List[Tuple3[String, Long, Long]]()
// set random seed for benchmark or debug
val seed = 1919810
Random.setSeed(seed)
def poke_write_transaction() {
val addr = Random.nextLong(0xFFFFFFFFL)
val data = Random.nextLong(0xFFFFFFFFL)
FlexibleTestHelper.init_write_transaction(c.io.master, c.clock, addr.U, data.U, 0xF.U)
c.io.master.busy.expect(true.B)
slave_trans_to_expect :+= ("write", addr, data)
}
def poke_read_transaction() {
val addr = Random.nextLong(0xFFFFFFFFL)
val data = Random.nextLong(0xFFFFFFFFL)
FlexibleTestHelper.init_read_transaction(c.io.master, c.clock, addr.U)
c.io.readout_data.poke(data.U)
slave_trans_to_expect :+= ("read", addr, data) // record
}
breakable { while (true) {
if (!c.io.master.busy.peekBoolean()) {
c.io.detach.poke(true.B) // to simulate that slave is busy and disconnect with master
// randomly poke read or write transactions
if (Random.nextBoolean()) poke_write_transaction()
else poke_read_transaction()
c.clock.step(3) // slave busy for this long time, while master waits for it
c.io.detach.poke(false.B)
}
if (slave_trans_to_expect.length > 0) {
val (trans_type, addr, data) = slave_trans_to_expect.head
if (trans_type == "write") {
if (c.io.slave.write.peekBoolean()) {
c.io.slave.address.expect(addr.U)
c.io.slave.write_data.expect(data.U)
for (i <- 0 until 4) c.io.slave.write_strobe(i).expect(1.U)
println(s"Write transaction to address 0x${addr.toHexString} with data 0x${data.toHexString} success")
num_success_transactions += 1
slave_trans_to_expect = slave_trans_to_expect.tail // pop the head
}
}
else {
if (c.io.slave.read.peekBoolean()) {
c.io.slave.address.expect(addr.U)
c.io.readout_data.expect(data.U)
println(s"Read transaction from address 0x${addr.toHexString} with expected data 0x${data.toHexString} success")
num_success_transactions += 1
slave_trans_to_expect = slave_trans_to_expect.tail // pop the head
}
}
}
if (num_success_transactions >= num_transactions) {
println(s"All $num_transactions transactions success in $total_cycles cycles with seed $seed")
break()
}
c.clock.step()
total_cycles += 1
}}
}
}
}

View File

@@ -14,12 +14,13 @@
package riscv.fivestage
import board.basys3.BootStates
import bus.BusSwitch
import scala.util.control.Breaks._
import chisel3._
import chisel3.util.{is, switch}
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
import board.basys3.BootStates
import bus.BusSwitch
import peripheral.{DummySlave, Memory, ROMLoader}
import riscv.core.fivestage.{CPU, ProgramCounter}
import riscv.{Parameters, TestAnnotations}
@@ -113,36 +114,57 @@ class TestTopModule(exeFilename: String) extends Module {
class FibonacciTest extends AnyFlatSpec with ChiselScalatestTester {
behavior of "Five Stage CPU"
behavior of "CPU"
it should "calculate recursively fibonacci(10)" in {
test(new TestTopModule("fibonacci.asmbin")).withAnnotations(TestAnnotations.annos) { c =>
c.clock.setTimeout(100 * 1000)
c.io.interrupt.poke(0.U)
for (i <- 1 to 100) {
c.clock.step(1000)
c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout
}
c.io.mem_debug_read_address.poke(0.U)
var cycle_passed = 0
breakable { while (true) {
c.clock.step()
cycle_passed += 1
if (c.io.mem_debug_read_data.peek().litValue == 0xbabecafeL) {
c.clock.step(100)
break()
}
}}
c.io.mem_debug_read_address.poke(4.U)
c.clock.step()
c.io.mem_debug_read_data.expect(55.U)
print(s"Cycles passed: $cycle_passed")
}
}
}
class QuicksortTest extends AnyFlatSpec with ChiselScalatestTester {
behavior of "Five Stage CPU"
behavior of "CPU"
it should "quicksort 10 numbers" in {
test(new TestTopModule("quicksort.asmbin")).withAnnotations(TestAnnotations.annos) { c =>
c.clock.setTimeout(50 * 1000)
c.io.interrupt.poke(0.U)
for (i <- 1 to 50) {
c.clock.step(1000)
c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout
}
c.io.mem_debug_read_address.poke(0.U)
var cycle_passed = 0
breakable { while (true) {
c.clock.step()
cycle_passed += 1
if (c.io.mem_debug_read_data.peek().litValue == 0xbabecafeL) {
c.clock.step(100)
break()
}
}}
for (i <- 1 to 10) {
c.io.mem_debug_read_address.poke((4 * i).U)
c.clock.step()
c.io.mem_debug_read_data.expect((i - 1).U)
}
print(s"Cycles passed: $cycle_passed")
}
}
}

View File

@@ -14,12 +14,13 @@
package riscv.threestage
import board.basys3.BootStates
import bus.BusSwitch
import scala.util.control.Breaks._
import chisel3._
import chisel3.util.{is, switch}
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
import board.basys3.BootStates
import bus.BusSwitch
import peripheral.{DummySlave, Memory, ROMLoader}
import riscv.core.threestage.{CPU, ProgramCounter}
import riscv.{Parameters, TestAnnotations}
@@ -116,15 +117,25 @@ class FibonacciTest extends AnyFlatSpec with ChiselScalatestTester {
behavior of "CPU"
it should "calculate recursively fibonacci(10)" in {
test(new TestTopModule("fibonacci.asmbin")).withAnnotations(TestAnnotations.annos) { c =>
c.clock.setTimeout(100 * 1000)
c.io.interrupt.poke(0.U)
for (i <- 1 to 100) {
c.clock.step(1000)
c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout
}
c.io.mem_debug_read_address.poke(0.U)
var cycle_passed = 0
breakable { while (true) {
c.clock.step()
cycle_passed += 1
if (c.io.mem_debug_read_data.peek().litValue == 0xbabecafeL) {
c.clock.step(100)
break()
}
}}
c.io.mem_debug_read_address.poke(4.U)
c.clock.step()
c.io.mem_debug_read_data.expect(55.U)
print(s"Cycles passed: $cycle_passed")
}
}
}
@@ -133,16 +144,27 @@ class QuicksortTest extends AnyFlatSpec with ChiselScalatestTester {
behavior of "CPU"
it should "quicksort 10 numbers" in {
test(new TestTopModule("quicksort.asmbin")).withAnnotations(TestAnnotations.annos) { c =>
c.clock.setTimeout(50 * 1000)
c.io.interrupt.poke(0.U)
for (i <- 1 to 50) {
c.clock.step(1000)
c.io.mem_debug_read_address.poke((i * 4).U) // Avoid timeout
}
c.io.mem_debug_read_address.poke(0.U)
var cycle_passed = 0
breakable { while (true) {
c.clock.step()
cycle_passed += 1
if (c.io.mem_debug_read_data.peek().litValue == 0xbabecafeL) {
c.clock.step(100)
break()
}
}}
for (i <- 1 to 10) {
c.io.mem_debug_read_address.poke((4 * i).U)
c.clock.step()
c.io.mem_debug_read_data.expect((i - 1).U)
}
print(s"Cycles passed: $cycle_passed")
}
}
}