mirror of
https://github.com/handsomezhuzhu/2025-yatcpu.git
synced 2026-02-20 20:10:14 +00:00
lab4 tutorial updated and imported some docs from orginal YatCPU docs
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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
3
lab4/csrc/build.cl.bat
Normal 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
3
lab4/csrc/build.cl.sh
Normal 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
3
lab4/csrc/build.gnu.bat
Normal 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
3
lab4/csrc/build.gnu.sh
Normal 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`
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
rm -rf build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -B build . && cmake --build build --parallel `nproc`
|
||||
@@ -19,4 +19,6 @@ int fib(int a) {
|
||||
|
||||
int main() {
|
||||
*(int *)(4) = fib(10);
|
||||
*(int*)(0) = 0xbabecafe; // anchor for program finished
|
||||
return 0;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
21
lab4/csrc/toolchain.riscv-gnu.cmake
Normal file
21
lab4/csrc/toolchain.riscv-gnu.cmake
Normal 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")
|
||||
Binary file not shown.
Binary file not shown.
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user