diff --git a/build.sc b/build.sc index 2cf8c7f02c..4a711d5d63 100644 --- a/build.sc +++ b/build.sc @@ -2,6 +2,7 @@ import mill._ import mill.scalalib._ import mill.scalalib.publish._ import coursier.maven.MavenRepository +import mill.scalalib.scalafmt.ScalafmtModule import $file.hardfloat.common import $file.cde.common import $file.common @@ -108,6 +109,150 @@ trait RocketChipPublishModule override def publishVersion: T[String] = T("1.6-SNAPSHOT") } +object tests extends Cross[Tests](v.chiselCrossVersions.keys.toSeq) + +trait Tests + extends millbuild.common.TestsModule + with ScalafmtModule + with RocketChipPublishModule + with Cross.Module[String] { + def scalaVersion: T[String] = T(v.scala) + def rocketchipModule = rocketchip(crossValue) +} + +object testbench extends Cross[Testbench]( + ("freechips.rocketchip.system.ExampleRocketSystem", "freechips.rocketchip.system.DefaultConfig") +) + +trait Testbench extends Cross.Module2[String, String] { + val top: String = crossValue + val config: String = crossValue2 + + object generator extends Module { + def elaborate = T { + os.proc( + mill.util.Jvm.javaExe, + "-jar", + tests(v.chiselCrossVersions.keys.last).assembly().path, + "--dir", T.dest.toString, + "--top", top, + config.split('_').flatMap(c => Seq("--config", c)), + ).call() + PathRef(T.dest) + } + + def chiselAnno = T { + os.walk(elaborate().path).collectFirst { case p if p.last.endsWith("anno.json") => p }.map(PathRef(_)).get + } + + def chirrtl = T { + os.walk(elaborate().path).collectFirst { case p if p.last.endsWith("fir") => p }.map(PathRef(_)).get + } + } + + object mfccompiler extends Module { + def compile = T { + os.proc("firtool", + generator.chirrtl().path, + s"--annotation-file=${generator.chiselAnno().path}", + "--disable-annotation-unknown", + "-dedup", + "-O=debug", + "--split-verilog", + "--preserve-values=named", + "--output-annotation-file=mfc.anno.json", + s"-o=${T.dest}" + ).call(T.dest) + PathRef(T.dest) + } + + def rtls = T { + os.read(compile().path / "filelist.f").split("\n").map(str => + try { + os.Path(str) + } catch { + case e: IllegalArgumentException if e.getMessage.contains("is not an absolute path") => + compile().path / str.stripPrefix("./") + } + ).filter(p => p.ext == "v" || p.ext == "sv").map(PathRef(_)).toSeq + } + } + + object verilator extends Module { + def csrcDir = T.source { + tests(v.chiselCrossVersions.keys.last).millSourcePath / "csrc" + } + + def allCSourceFiles = T.sources { + Seq( + "dpic.cc", + ).map(f => PathRef(csrcDir().path / f)) + } + + def CMakeListsString = T { + // format: off + s"""cmake_minimum_required(VERSION 3.20) + |project(emulator) + |include_directories(${csrcDir().path}) + |# plusarg is here + |include_directories(${generator.elaborate().path}) + | + |set(CMAKE_BUILD_TYPE Release) + |set(CMAKE_CXX_STANDARD 17) + |set(CMAKE_C_COMPILER "clang") + |set(CMAKE_CXX_COMPILER "clang++") + |set(THREADS_PREFER_PTHREAD_FLAG ON) + | + |find_package(verilator) + |find_package(Threads) + | + |add_executable(emulator + |${allCSourceFiles().map(_.path).mkString("\n")} + |) + | + |target_link_libraries(emulator PRIVATE $${CMAKE_THREAD_LIBS_INIT}) + |verilate(emulator + | SOURCES + | ${mfccompiler.rtls().sortBy(pr => !pr.path.baseName.startsWith("ref_")).map(_.path.toString).mkString("\n")} + | TOP_MODULE Testbench + | PREFIX VTestbench + | TRACE + | VERILATOR_ARGS ${verilatorArgs().mkString(" ")} + |) + |""".stripMargin + // format: on + } + + def verilatorArgs = T { + Seq( + // format: off + "--x-initial unique", + "--output-split 100000", + "--max-num-width 1048576", + "--main", + "--timing", + // use for coverage + "--coverage-user", + "--assert", + "--Wno-WIDTHEXPAND", + // format: on + ) + } + + def cmakefileLists = T.persistent { + val path = T.dest / "CMakeLists.txt" + os.write.over(path, CMakeListsString()) + PathRef(T.dest) + } + + def elf = T.persistent { + mill.util.Jvm.runSubprocess(Seq("cmake", "-G", "Ninja", "-S", cmakefileLists().path, "-B", T.dest.toString).map(_.toString), Map[String, String](), T.dest) + mill.util.Jvm.runSubprocess(Seq("ninja", "-C", T.dest).map(_.toString), Map[String, String](), T.dest) + PathRef(T.dest / "emulator") + } + } + +} // Tests trait Emulator extends Cross.Module2[String, String] { diff --git a/common.sc b/common.sc index 574f0ad431..b5da6e459c 100644 --- a/common.sc +++ b/common.sc @@ -32,7 +32,6 @@ trait MacrosModule override def ivyDeps = T(super.ivyDeps() ++ Some(scalaReflectIvy)) } - trait RocketChipModule extends HasChisel { override def mainClass = T(Some("freechips.rocketchip.diplomacy.Main")) @@ -58,3 +57,17 @@ trait RocketChipModule ) ) } + +// Stores some non-public test only staffs +trait TestsModule + extends HasChisel { + def rocketchipModule: RocketChipModule + + def chiselModule: Option[ScalaModule] = rocketchipModule.chiselModule + def chiselPluginJar: T[Option[PathRef]] = T(rocketchipModule.chiselPluginJar()) + def chiselIvy: Option[Dep] = rocketchipModule.chiselIvy + def chiselPluginIvy: Option[Dep] = rocketchipModule.chiselPluginIvy + + override def mainClass = T(Some("org.chipsalliance.rocketchip.internal.tests.Main")) + override def moduleDeps = super.moduleDeps ++ Seq(rocketchipModule) +} \ No newline at end of file diff --git a/tests/csrc/auto_sig.hpp b/tests/csrc/auto_sig.hpp new file mode 100644 index 0000000000..ba824aab14 --- /dev/null +++ b/tests/csrc/auto_sig.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#define AUTO_TYPE(msb, lsb) \ + typename std::conditional< \ + ((msb) - (lsb) + 1) <= 8, CData, \ + typename std::conditional< \ + ((msb) - (lsb) + 1) <= 16, SData, \ + typename std::conditional<((msb) - (lsb) + 1) <= 32, IData, \ + QData>::type>::type>::type + +#define AUTO_SIG(name, msb, lsb) AUTO_TYPE(msb, lsb) name + +#define AUTO_IN(name, msb, lsb) AUTO_SIG(name, msb, lsb) +#define AUTO_OUT(name, msb, lsb) AUTO_SIG(name, msb, lsb) \ No newline at end of file diff --git a/tests/csrc/axi4.hpp b/tests/csrc/axi4.hpp new file mode 100644 index 0000000000..7820b5635f --- /dev/null +++ b/tests/csrc/axi4.hpp @@ -0,0 +1,259 @@ +#pragma once + +#include "auto_sig.hpp" +#include +#include +#include + +/* + We have defined 3 types of AXI signals for a different purposes: axi4, + axi4_ptr, axi4_ref. + + Since verilator exposes signals as a value itself, we use axi4_ptr to get + signal to connect. + + Then axi4_ptr can be converted to axi4_ref to reach the value in a better + way. + */ + +template +struct axi4; + +template +struct axi4_ptr { + static_assert(__builtin_popcount(D_WIDTH) == 1, + "D_WIDTH should be the power of 2."); + static_assert(D_WIDTH >= 8, "D_WIDTH should be larger or equal to 8."); + // aw + AUTO_IN(*awid, ID_WIDTH - 1, 0) = NULL; + AUTO_IN(*awaddr, A_WIDTH - 1, 0) = NULL; + AUTO_IN(*awlen, 7, 0) = NULL; + AUTO_IN(*awsize, 2, 0) = NULL; + AUTO_IN(*awburst, 1, 0) = NULL; + AUTO_IN(*awvalid, 0, 0) = NULL; + AUTO_OUT(*awready, 0, 0) = NULL; + // w + AUTO_IN(*wdata, D_WIDTH - 1, 0) = NULL; + AUTO_IN(*wstrb, (D_WIDTH / 8) - 1, 0) = NULL; + AUTO_IN(*wlast, 0, 0) = NULL; + AUTO_IN(*wvalid, 0, 0) = NULL; + AUTO_OUT(*wready, 0, 0) = NULL; + // b + AUTO_OUT(*bid, ID_WIDTH - 1, 0) = NULL; + AUTO_OUT(*bresp, 1, 0) = NULL; + AUTO_OUT(*bvalid, 0, 0) = NULL; + AUTO_IN(*bready, 0, 0) = NULL; + // ar + AUTO_IN(*arid, ID_WIDTH - 1, 0) = NULL; + AUTO_IN(*araddr, A_WIDTH - 1, 0) = NULL; + AUTO_IN(*arlen, 7, 0) = NULL; + AUTO_IN(*arsize, 2, 0) = NULL; + AUTO_IN(*arburst, 1, 0) = NULL; + AUTO_IN(*arvalid, 0, 0) = NULL; + AUTO_OUT(*arready, 0, 0) = NULL; + // r + AUTO_OUT(*rid, ID_WIDTH - 1, 0) = NULL; + AUTO_OUT(*rdata, D_WIDTH - 1, 0) = NULL; + AUTO_OUT(*rresp, 1, 0) = NULL; + AUTO_OUT(*rlast, 0, 0) = NULL; + AUTO_OUT(*rvalid, 0, 0) = NULL; + AUTO_IN(*rready, 0, 0) = NULL; + bool check() { + std::set s; + // aw + s.insert((void *)awid); + s.insert((void *)awaddr); + s.insert((void *)awlen); + s.insert((void *)awsize); + s.insert((void *)awburst); + s.insert((void *)awvalid); + s.insert((void *)awready); + // w + s.insert((void *)wdata); + s.insert((void *)wstrb); + s.insert((void *)wlast); + s.insert((void *)wvalid); + s.insert((void *)wready); + // b + s.insert((void *)bid); + s.insert((void *)bresp); + s.insert((void *)bvalid); + s.insert((void *)bready); + // ar + s.insert((void *)arid); + s.insert((void *)araddr); + s.insert((void *)arlen); + s.insert((void *)arsize); + s.insert((void *)arburst); + s.insert((void *)arvalid); + s.insert((void *)arready); + // r + s.insert((void *)rid); + s.insert((void *)rdata); + s.insert((void *)rresp); + s.insert((void *)rlast); + s.insert((void *)rvalid); + s.insert((void *)rready); + return s.size() == 29 && s.count(NULL) == 0; + } +}; + +template +struct axi4_ref { + AUTO_IN(&awid, ID_WIDTH - 1, 0); + AUTO_IN(&awaddr, A_WIDTH - 1, 0); + AUTO_IN(&awlen, 7, 0); + AUTO_IN(&awsize, 2, 0); + AUTO_IN(&awburst, 1, 0); + AUTO_IN(&awvalid, 0, 0); + AUTO_OUT(&awready, 0, 0); + // w + AUTO_IN(&wdata, D_WIDTH - 1, 0); + AUTO_IN(&wstrb, (D_WIDTH / 8) - 1, 0); + AUTO_IN(&wlast, 0, 0); + AUTO_IN(&wvalid, 0, 0); + AUTO_OUT(&wready, 0, 0); + // b + AUTO_OUT(&bid, ID_WIDTH - 1, 0); + AUTO_OUT(&bresp, 1, 0); + AUTO_OUT(&bvalid, 0, 0); + AUTO_IN(&bready, 0, 0); + // ar + AUTO_IN(&arid, ID_WIDTH - 1, 0); + AUTO_IN(&araddr, A_WIDTH - 1, 0); + AUTO_IN(&arlen, 7, 0); + AUTO_IN(&arsize, 2, 0); + AUTO_IN(&arburst, 1, 0); + AUTO_IN(&arvalid, 0, 0); + AUTO_OUT(&arready, 0, 0); + // r + AUTO_OUT(&rid, ID_WIDTH - 1, 0); + AUTO_OUT(&rdata, D_WIDTH - 1, 0); + AUTO_OUT(&rresp, 1, 0); + AUTO_OUT(&rlast, 0, 0); + AUTO_OUT(&rvalid, 0, 0); + AUTO_IN(&rready, 0, 0); + axi4_ref(axi4_ptr &ptr) + : awid(*(ptr.awid)), awaddr(*(ptr.awaddr)), awlen(*(ptr.awlen)), + awsize(*(ptr.awsize)), awburst(*(ptr.awburst)), awvalid(*(ptr.awvalid)), + awready(*(ptr.awready)), wdata(*(ptr.wdata)), wstrb(*(ptr.wstrb)), + wlast(*(ptr.wlast)), wvalid(*(ptr.wvalid)), wready(*(ptr.wready)), + bid(*(ptr.bid)), bresp(*(ptr.bresp)), bvalid(*(ptr.bvalid)), + bready(*(ptr.bready)), arid(*(ptr.arid)), araddr(*(ptr.araddr)), + arlen(*(ptr.arlen)), arsize(*(ptr.arsize)), arburst(*(ptr.arburst)), + arvalid(*(ptr.arvalid)), arready(*(ptr.arready)), rid(*(ptr.rid)), + rdata(*(ptr.rdata)), rresp(*(ptr.rresp)), rlast(*(ptr.rlast)), + rvalid(*(ptr.rvalid)), rready(*(ptr.rready)) {} + + axi4_ref(axi4 &axi4); +}; + +template +struct axi4 { + AUTO_IN(awid, ID_WIDTH - 1, 0); + AUTO_IN(awaddr, A_WIDTH - 1, 0); + AUTO_IN(awlen, 7, 0); + AUTO_IN(awsize, 2, 0); + AUTO_IN(awburst, 1, 0); + AUTO_IN(awvalid, 0, 0); + AUTO_OUT(awready, 0, 0); + // w + AUTO_IN(wdata, D_WIDTH - 1, 0); + AUTO_IN(wstrb, (D_WIDTH / 8) - 1, 0); + AUTO_IN(wlast, 0, 0); + AUTO_IN(wvalid, 0, 0); + AUTO_OUT(wready, 0, 0); + // b + AUTO_OUT(bid, ID_WIDTH - 1, 0); + AUTO_OUT(bresp, 1, 0); + AUTO_OUT(bvalid, 0, 0); + AUTO_IN(bready, 0, 0); + // ar + AUTO_IN(arid, ID_WIDTH - 1, 0); + AUTO_IN(araddr, A_WIDTH - 1, 0); + AUTO_IN(arlen, 7, 0); + AUTO_IN(arsize, 2, 0); + AUTO_IN(arburst, 1, 0); + AUTO_IN(arvalid, 0, 0); + AUTO_OUT(arready, 0, 0); + // r + AUTO_OUT(rid, ID_WIDTH - 1, 0); + AUTO_OUT(rdata, D_WIDTH - 1, 0); + AUTO_OUT(rresp, 1, 0); + AUTO_OUT(rlast, 0, 0); + AUTO_OUT(rvalid, 0, 0); + AUTO_IN(rready, 0, 0); + axi4() { + // reset all pointer to zero + memset(this, 0, sizeof(*this)); + } + void update_input(axi4_ref &ref) { + // aw + awid = ref.awid; + awaddr = ref.awaddr; + awlen = ref.awlen; + awsize = ref.awsize; + awburst = ref.awburst; + awvalid = ref.awvalid; + // w + wdata = ref.wdata; + wstrb = ref.wstrb; + wlast = ref.wlast; + wvalid = ref.wvalid; + // b + bready = ref.bready; + // arid + arid = ref.arid; + araddr = ref.araddr; + arlen = ref.arlen; + arsize = ref.arsize; + arburst = ref.arburst; + arvalid = ref.arvalid; + // r + rready = ref.rready; + } + void update_output(axi4_ref &ref) { + ref.awready = awready; + ref.wready = wready; + ref.bid = bid; + ref.bresp = bresp; + ref.bvalid = bvalid; + ref.arready = arready; + ref.rid = rid; + ref.rdata = rdata; + ref.rresp = rresp; + ref.rlast = rlast; + ref.rvalid = rvalid; + } +}; + +template +axi4_ref::axi4_ref( + axi4 &axi4) + : awid(axi4.awid), awaddr(axi4.awaddr), awlen(axi4.awlen), + awsize(axi4.awsize), awburst(axi4.awburst), awvalid(axi4.awvalid), + awready(axi4.awready), wdata(axi4.wdata), wstrb(axi4.wstrb), + wlast(axi4.wlast), wvalid(axi4.wvalid), wready(axi4.wready), + bid(axi4.bid), bresp(axi4.bresp), bvalid(axi4.bvalid), + bready(axi4.bready), arid(axi4.arid), araddr(axi4.araddr), + arlen(axi4.arlen), arsize(axi4.arsize), arburst(axi4.arburst), + arvalid(axi4.arvalid), arready(axi4.arready), rid(axi4.rid), + rdata(axi4.rdata), rresp(axi4.rresp), rlast(axi4.rlast), + rvalid(axi4.rvalid), rready(axi4.rready) {} + +enum axi_resp { + RESP_OKEY = 0, + RESP_EXOKEY = 1, + RESP_SLVERR = 2, + RESP_DECERR = 3 +}; + +enum axi_burst_type { + BURST_FIXED = 0, + BURST_INCR = 1, + BURST_WRAP = 2, + BURST_RESERVED = 3 +}; diff --git a/tests/csrc/axi4_mem.hpp b/tests/csrc/axi4_mem.hpp new file mode 100644 index 0000000000..567202d78f --- /dev/null +++ b/tests/csrc/axi4_mem.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include "axi4_slave.hpp" +#include +#include +#include + +template +class axi4_mem : public axi4_slave { +public: + axi4_mem(size_t size_bytes, bool allow_warp = false) + : allow_warp(allow_warp) { + if (size_bytes % (D_WIDTH / 8)) + size_bytes += 8 - (size_bytes % (D_WIDTH / 8)); + mem = new unsigned char[size_bytes]; + mem_size = size_bytes; + } + axi4_mem(size_t size_bytes, const uint8_t *init_binary, + size_t init_binary_len, bool allow_warp = false) + : axi4_mem(size_bytes, allow_warp) { + assert(init_binary_len <= size_bytes); + memcpy(mem, init_binary, init_binary_len); + } + axi4_mem(size_t size_bytes, const char *filename, bool allow_warp = false) + : axi4_mem(size_bytes, allow_warp) { + load_binary(filename); + } + ~axi4_mem() { delete[] mem; } + bool read(off_t start_addr, size_t size, uint8_t *buffer) { + return do_read(start_addr, size, buffer) == RESP_OKEY; + } + bool write(off_t start_addr, size_t size, const uint8_t *buffer) { + return do_write(start_addr, size, buffer) == RESP_OKEY; + } + void load_binary(const char *init_file, uint64_t start_addr = 0) { + std::ifstream fs(init_file, std::ios::binary); + fs.exceptions(std::ios::failbit); + + Elf32_Ehdr ehdr; + fs.read(reinterpret_cast(&ehdr), sizeof(ehdr)); + if (!(ehdr.e_machine == EM_RISCV && ehdr.e_type == ET_EXEC && + ehdr.e_ident[EI_CLASS] == ELFCLASS32)) { + std::cerr << "ehdr check failed when loading elf" << std::endl; + } + if (ehdr.e_phentsize != sizeof(elf32_phdr)) { + std::cerr << "ehdr.e_phentsize does not equal to elf32_phdr" << std::endl; + } + + for (size_t i = 0; i < ehdr.e_phnum; i++) { + auto phdr_offset = ehdr.e_phoff + i * ehdr.e_phentsize; + Elf32_Phdr phdr; + fs.seekg((long)phdr_offset) + .read(reinterpret_cast(&phdr), sizeof(phdr)); + if (phdr.p_type == PT_LOAD) { + if (phdr.p_paddr + phdr.p_filesz >= mem_size) { + std::cerr << "phdr p_paddr + p_filesz check failed" << std::endl; + } + fs.seekg((long)phdr.p_offset) + .read(reinterpret_cast(&mem[phdr.p_paddr]), phdr.p_filesz); + } + } + entry_addr = ehdr.e_entry; + } + uint32_t get_entry_addr() { return entry_addr; } + +protected: + axi_resp do_read(uint64_t start_addr, uint64_t size, uint8_t *buffer) { + if (allow_warp) + start_addr %= mem_size; + if (start_addr + size <= mem_size) { + memcpy(buffer, &mem[start_addr], size); + return RESP_OKEY; + } else + return RESP_DECERR; + } + axi_resp do_write(uint64_t start_addr, uint64_t size, const uint8_t *buffer) { + if (allow_warp) + start_addr %= mem_size; + if (start_addr + size <= mem_size) { + memcpy(&mem[start_addr], buffer, size); + return RESP_OKEY; + } else + return RESP_DECERR; + } + +private: + uint8_t *mem; + size_t mem_size; + uint32_t entry_addr; + bool allow_warp = false; +}; diff --git a/tests/csrc/axi4_slave.hpp b/tests/csrc/axi4_slave.hpp new file mode 100644 index 0000000000..36763ce859 --- /dev/null +++ b/tests/csrc/axi4_slave.hpp @@ -0,0 +1,318 @@ +#pragma once + +#include "axi4.hpp" + +#include +#include +#include +#include + +template +class axi4_slave { + static_assert(D_WIDTH <= 64, + "D_WIDTH should be <= 64."); // TODO: larger D_WIDTH support + static_assert(A_WIDTH <= 64, "A_WIDTH should be <= 64."); + +public: + axi4_slave(int delay = 0) : delay(delay) {} + void beat(axi4_ref &pin) { + read_channel(pin); + write_channel(pin); + } + void reset() { + read_busy = false; + read_last = false; + read_wait = false; + read_delay = 0; + write_busy = false; + b_busy = false; + write_delay = 0; + } + +protected: + virtual axi_resp do_read(uint64_t start_addr, uint64_t size, + uint8_t *buffer) = 0; + virtual axi_resp do_write(uint64_t start_addr, uint64_t size, + const uint8_t *buffer) = 0; + +private: + unsigned int D_bytes = D_WIDTH / 8; + int delay; + +private: + bool read_busy = false; // during trascation except last + bool read_last = false; // wait rready and free + bool read_wait = false; // ar ready, but waiting the last read to ready + int read_delay = 0; // delay + uint64_t r_start_addr; // lower bound of transaction address + uint64_t r_current_addr; // current burst address in r_data buffer (physical + // address % 4096) + AUTO_SIG(arid, ID_WIDTH - 1, 0); + axi_burst_type r_burst_type; + unsigned int r_each_len; + unsigned int r_nr_trans; + unsigned int r_cur_trans; + unsigned int r_tot_len; + bool r_out_ready; + bool r_early_err; + axi_resp r_resp; + uint8_t r_data[4096]; + + bool read_check() { + if (r_burst_type == BURST_RESERVED) + return false; + if (r_burst_type == BURST_WRAP && (r_current_addr % r_each_len)) + return false; + if (r_burst_type == BURST_WRAP) { + if (r_nr_trans != 2 && r_nr_trans != 4 && r_nr_trans != 8 && + r_nr_trans != 16) { + return false; + } + } + uint64_t rem_addr = 4096 - (r_start_addr % 4096); + if (r_tot_len > rem_addr) + return false; + if (r_each_len > D_bytes) + return false; + return true; + } + + void read_beat(axi4_ref &pin) { + pin.rid = arid; + pin.rvalid = 1; + bool update = false; + if (pin.rready || r_cur_trans == 0) { + r_cur_trans += 1; + update = true; + if (r_cur_trans == r_nr_trans) { + read_last = true; + read_busy = false; + } + } + pin.rlast = read_last; + if (update) { + if (r_early_err) { + pin.rresp = RESP_DECERR; + pin.rdata = 0; + } else if (r_burst_type == BURST_FIXED) { + pin.rresp = do_read(static_cast(r_start_addr), + static_cast(r_tot_len), + &r_data[r_start_addr % 4096]); + pin.rdata = *(AUTO_SIG(*, D_WIDTH - 1, 0))( + &r_data[(r_start_addr % 4096) - (r_start_addr % D_bytes)]); + } else { // INCR, WRAP + pin.rresp = r_resp; + pin.rdata = *(AUTO_SIG(*, D_WIDTH - 1, 0))( + &r_data[r_current_addr - (r_current_addr % D_bytes)]); + r_current_addr += r_each_len - (r_current_addr % r_each_len); + if (r_burst_type == BURST_WRAP && + r_current_addr == + ((r_start_addr % 4096) + r_each_len * r_nr_trans)) { + r_current_addr = r_start_addr % 4096; + } + } + } + } + + void read_init(axi4_ref &pin) { + arid = static_cast(pin.arid); + r_burst_type = static_cast(pin.arburst); + r_each_len = 1 << pin.arsize; + r_nr_trans = pin.arlen + 1; + r_current_addr = (r_burst_type == BURST_WRAP) + ? (pin.araddr % 4096) + : ((pin.araddr % 4096) - (pin.araddr % r_each_len)); + r_start_addr = (r_burst_type == BURST_WRAP) + ? (pin.araddr - (pin.araddr % (r_each_len * r_nr_trans))) + : pin.araddr; + r_cur_trans = 0; + r_tot_len = + ((r_burst_type == BURST_FIXED) ? r_each_len : r_each_len * r_nr_trans) - + (r_start_addr % r_each_len); // first beat can be unaligned + r_early_err = !read_check(); + assert(!r_early_err); + // clear unused bits. + if (r_start_addr % D_bytes) { + uint64_t clear_addr = r_start_addr % 4096; + clear_addr -= clear_addr % D_bytes; + memset(&r_data[clear_addr], 0x00, D_bytes); + } + if ((r_start_addr + r_tot_len) % D_bytes) { + uint64_t clear_addr = (r_start_addr + r_tot_len) % 4096; + clear_addr -= (clear_addr % D_bytes); + memset(&r_data[clear_addr], 0x00, D_bytes); + } + // For BURST_FIXED, we call do_read every read burst + if (!r_early_err && r_burst_type != BURST_FIXED) + r_resp = do_read(static_cast(r_start_addr), + static_cast(r_tot_len), + &r_data[r_start_addr % 4096]); + } + + void read_channel(axi4_ref &pin) { + // Read step 1. release old transaction + if (read_last && pin.rready) { + read_last = false; + pin.rvalid = 0; // maybe change in the following code + pin.rlast = 0; + if (read_wait) { + read_wait = false; + read_busy = true; + read_delay = delay; + } + } + // Read step 2. check new address come + if (pin.arready && pin.arvalid) { + read_init(pin); + if (read_last) + read_wait = true; + else { + read_busy = true; + read_delay = delay; + } + } + // Read step 3. do read trascation + if (read_busy) { + if (read_delay) + read_delay--; + else + read_beat(pin); + } + // Read step 4. set arready before new address come, it will change + // read_busy and read_wait status + pin.arready = !read_busy && !read_wait; + } + +private: + bool write_busy = false; + bool b_busy = false; + int write_delay = 0; + uint64_t w_start_addr; + uint64_t w_current_addr; + AUTO_SIG(awid, ID_WIDTH - 1, 0); + axi_burst_type w_burst_type; + unsigned int w_each_len; + int w_nr_trans; + int w_cur_trans; + unsigned int w_tot_len; + bool w_out_ready; + bool w_early_err; + axi_resp w_resp; + uint8_t w_buffer[D_WIDTH / 8]; + bool write_check() { + if (w_burst_type == BURST_RESERVED) + return false; + if (w_burst_type == BURST_WRAP && (w_current_addr % w_each_len)) + return false; + if (w_burst_type == BURST_WRAP) { + if (w_nr_trans != 2 && w_nr_trans != 4 && w_nr_trans != 8 && + w_nr_trans != 16) + return false; + } + uint64_t rem_addr = 4096 - (w_start_addr % 4096); + if (w_tot_len > rem_addr) + return false; + if (w_each_len > D_bytes) + return false; + return true; + } + void write_init(axi4_ref &pin) { + awid = pin.awid; + w_burst_type = static_cast(pin.awburst); + w_each_len = 1 << pin.awsize; + w_nr_trans = pin.awlen + 1; + w_current_addr = (w_burst_type == BURST_WRAP) + ? pin.awaddr + : (pin.awaddr - (pin.awaddr % w_each_len)); + w_start_addr = (w_burst_type == BURST_WRAP) + ? (pin.awaddr - (pin.awaddr % (w_each_len * w_nr_trans))) + : pin.awaddr; + w_cur_trans = 0; + w_tot_len = w_each_len * w_nr_trans - (w_start_addr % w_each_len); + w_early_err = !write_check(); + assert(!w_early_err); + w_resp = RESP_OKEY; + } + // pair + std::vector> + strb_to_range(AUTO_IN(wstrb, (D_WIDTH / 8) - 1, 0), int st_pos, int ed_pos) { + std::vector> res; + int l = st_pos; + while (l < ed_pos) { + if ((wstrb >> l) & 1) { + int r = l; + while ((wstrb >> r) & 1 && r < ed_pos) + r++; + res.emplace_back(l, r - l); + l = r + 1; + } else + l++; + } + return res; + } + void write_beat(axi4_ref &pin) { + if (pin.wvalid && pin.wready) { + w_cur_trans += 1; + if (w_cur_trans == w_nr_trans) { + write_busy = false; + b_busy = true; + if (!pin.wlast) { + w_early_err = true; + assert(false); + } + } + if (w_early_err) + return; + uint64_t addr_base = w_current_addr; + if (w_burst_type != BURST_FIXED) { + w_current_addr += w_each_len - (addr_base % w_each_len); + if (w_current_addr == (w_start_addr + w_each_len * w_nr_trans)) + w_cur_trans = w_start_addr; // warp support + } + uint64_t in_data_pos = addr_base % D_bytes; + addr_base -= addr_base % D_bytes; + uint64_t rem_data_pos = + w_each_len - (in_data_pos % w_each_len); // unaligned support + // split discontinuous wstrb bits to small requests + std::vector> range = + strb_to_range(pin.wstrb, in_data_pos, in_data_pos + rem_data_pos); + for (std::pair sub_range : range) { + int &addr = sub_range.first; + int &len = sub_range.second; + memcpy(w_buffer, &(pin.wdata), sizeof(pin.wdata)); + w_resp = static_cast( + static_cast(w_resp) | + static_cast(do_write(addr_base + addr, len, w_buffer + addr))); + } + } + } + void b_beat(axi4_ref &pin) { + pin.bid = awid; + pin.bresp = w_early_err ? RESP_DECERR : w_resp; + if (pin.bvalid && pin.bready) + b_busy = false; + } + void write_channel(axi4_ref &pin) { + if (pin.awready && pin.awvalid) { + write_init(pin); + write_busy = true; + write_delay = delay; + } + if (write_busy) { + if (write_delay) + write_delay--; + else + write_beat(pin); + } + if (b_busy) { + b_beat(pin); + } + pin.bvalid = b_busy; + pin.awready = !write_busy && !b_busy; + if (delay) + pin.wready = write_busy && !write_delay; + else + pin.wready = !b_busy; + } +}; diff --git a/tests/csrc/dpic.cc b/tests/csrc/dpic.cc new file mode 100644 index 0000000000..f8be5928fb --- /dev/null +++ b/tests/csrc/dpic.cc @@ -0,0 +1,118 @@ +#include "VTestHarness__Dpi.h" +#include "axi4_mem.hpp" +#include "svdpi.h" +#include +#include +#include +#include +#include +#include + +#define DPI extern "C" +#define IN const +#define OUT + +DPI const char *plus_arg_val(IN char *param); + +std::string plusarg_read_str(std::string param) { + svSetScope(svGetScopeFromName("TOP.TestHarness.dpiPlusArg")); + param += "=%s"; + std::string res = std::string(plus_arg_val(param.c_str())); + std::cout << "plusarg got [" << param << "]=[" << res << "]\n"; + return res; +} + +axi4_mem<30, 32, 4> ram(0x20000000, true); +axi4<30, 32, 4> mem_sigs; +uint32_t entry_addr; + +DPI void reset_vector(svBitVecVal *resetVector) { *resetVector = entry_addr; } + +DPI void init_cosim() { + // read plusarg + std::string trace_file = plusarg_read_str("trace_file"); + std::string init_file = plusarg_read_str("init_file"); + // init dumpwave + if (trace_file != "") { + svSetScope(svGetScopeFromName("TOP.TestHarness.dpiDumpWave")); + dump_wave(trace_file.c_str()); + } + // sigint signal + std::signal(SIGINT, [](int) { + svSetScope(svGetScopeFromName("TOP.TestHarness.dpiFinish")); + finish(); + }); + // init memory file + if (init_file != "") { + ram.load_binary(init_file.c_str()); + entry_addr = ram.get_entry_addr(); + std::cout << "set reset vector to " << entry_addr << "\n"; + } +} + +extern "C" void +AXI4BFMDPI(IN svBitVecVal *arid, IN svBitVecVal *araddr, IN svBitVecVal *arlen, + IN svBitVecVal *arsize, IN svBitVecVal *arburst, IN svLogic arvalid, + OUT svLogic *arready, OUT svBitVecVal *rid, OUT svBitVecVal *rdata, + OUT svLogic *rlast, OUT svBitVecVal *rresp, OUT svLogic *rvalid, + IN svLogic rready, IN svBitVecVal *awid, IN svBitVecVal *awaddr, + IN svBitVecVal *awlen, IN svBitVecVal *awsize, + IN svBitVecVal *awburst, IN svLogic awvalid, OUT svLogic *awready, + IN svBitVecVal *wdata, IN svLogic wlast, IN svBitVecVal *wstrb, + IN svLogic wvalid, OUT svLogic *wready, OUT svBitVecVal *bid, + OUT svBitVecVal *bresp, OUT svLogic *bvalid, IN svLogic bready) { + + // CTRL START { + axi4_ref<30, 32, 4> ref(mem_sigs); + ram.beat(ref); + // CTRL END } + + // output ar + *arready = mem_sigs.arready; + + // output r + *rid = mem_sigs.rid; + *rdata = mem_sigs.rdata; + *rlast = mem_sigs.rlast; + *rresp = mem_sigs.rresp; + *rvalid = mem_sigs.rvalid; + + // output aw + *awready = mem_sigs.awready; + + // output w + *wready = mem_sigs.wready; + + // output b + *bid = mem_sigs.bid; + *bresp = mem_sigs.bresp; + *bvalid = mem_sigs.bvalid; + + // input ar + mem_sigs.arid = *arid; + mem_sigs.araddr = *araddr; + mem_sigs.arlen = *arlen; + mem_sigs.arsize = *arsize; + mem_sigs.arburst = *arburst; + mem_sigs.arvalid = arvalid; + + // input r + mem_sigs.rready = rready; + + // input aw + mem_sigs.awid = *awid; + mem_sigs.awaddr = *awaddr; + mem_sigs.awlen = *awlen; + mem_sigs.awsize = *awsize; + mem_sigs.awburst = *awburst; + mem_sigs.awvalid = awvalid; + + // input w + mem_sigs.wdata = *wdata; + mem_sigs.wstrb = *wstrb; + mem_sigs.wlast = wlast; + mem_sigs.wvalid = wvalid; + + // input b + mem_sigs.bready = bready; +} \ No newline at end of file diff --git a/tests/src/LazyAXI4MemBFM.scala b/tests/src/LazyAXI4MemBFM.scala new file mode 100644 index 0000000000..a5fd9aeca6 --- /dev/null +++ b/tests/src/LazyAXI4MemBFM.scala @@ -0,0 +1,154 @@ +package org.chipsalliance.rocketchip.internal.tests + +import chisel3._ +import freechips.rocketchip.amba.AMBACorrupt +import freechips.rocketchip.amba.axi4._ +import freechips.rocketchip.diplomacy._ +import org.chipsalliance.cde.config.Parameters + +// In the future, the BFM framework should borrow the idea from CIRCT ESI Dialect providing channel based BFM for smoothly hook any BFM to bus +// For now, we only use this simple BFM memory as test program loader for loading program to test the subsystem. +class LazyAXI4MemBFM(edge: AXI4EdgeParameters, size: BigInt, base: BigInt = 0)(implicit p: Parameters) + extends SimpleLazyModule { + val node = AXI4MasterNode(List(edge.master)) + val bfms = AddressSet.misaligned(base, size).map { aSet => + LazyModule( + new AXI4BFM( + address = aSet, + beatBytes = edge.bundle.dataBits / 8, + wcorrupt = edge.slave.requestKeys.contains(AMBACorrupt) + ) + ) + } + val xbar = AXI4Xbar() + bfms.foreach { s => s.node := AXI4Buffer() := xbar } + xbar := node + val io_axi4 = InModuleBody { node.makeIOs() } +} + +class AXI4BFM( + address: AddressSet, + cacheable: Boolean = true, + executable: Boolean = true, + beatBytes: Int = 4, + errors: Seq[AddressSet] = Nil, + wcorrupt: Boolean = true +)( + implicit p: Parameters) + extends LazyModule { outer => + val node = AXI4SlaveNode( + Seq( + AXI4SlavePortParameters( + Seq( + AXI4SlaveParameters( + address = List(address) ++ errors, + resources = Nil, + regionType = if (cacheable) RegionType.UNCACHED else RegionType.IDEMPOTENT, + executable = executable, + supportsRead = TransferSizes(1, beatBytes), + supportsWrite = TransferSizes(1, beatBytes), + interleavedId = Some(0) + ) + ), + beatBytes = beatBytes, + requestKeys = if (wcorrupt) Seq(AMBACorrupt) else Seq(), + minLatency = 1 + ) + ) + ) + + lazy val module = new Impl + class Impl extends LazyModuleImp(this) { + val axi4Bundle: AXI4Bundle = node.in.head._1 + val bundleTpe = axi4Bundle.cloneType + val dpiGen = Module(new DPIModule { + override def desiredName = "AXI4BFMDPI" + override val isImport: Boolean = true + val clock = dpiTrigger("clock", Input(Bool())) + val reset = dpiTrigger("reset", Input(Bool())) + override val trigger: String = s"always@(negedge ${clock.name})" + override val guard: String = s"!${reset.name}" + + // ar channel + val arid = dpi("arid", Input(bundleTpe.ar.bits.id)) + val araddr = dpi("araddr", Input(bundleTpe.ar.bits.addr)) + val arlen = dpi("arlen", Input(bundleTpe.ar.bits.len)) + val arsize = dpi("arsize", Input(bundleTpe.ar.bits.size)) + val arburst = dpi("arburst", Input(bundleTpe.ar.bits.burst)) + val arvalid = dpi("arvalid", Input(bundleTpe.ar.valid)) + val arready = dpi("arready", Output(bundleTpe.ar.ready)) + + // r channel + val rid = dpi("rid", Output(bundleTpe.r.bits.id)) + val rdata = dpi("rdata", Output(bundleTpe.r.bits.data)) + val rlast = dpi("rlast", Output(bundleTpe.r.bits.last)) + val rresp = dpi("rresp", Output(bundleTpe.r.bits.resp)) + val rvalid = dpi("rvalid", Output(bundleTpe.r.valid)) + val rready = dpi("rready", Input(bundleTpe.r.ready)) + + // aw channel + val awid = dpi("awid", Input(bundleTpe.aw.bits.id)) + val awaddr = dpi("awaddr", Input(bundleTpe.aw.bits.addr)) + val awlen = dpi("awlen", Input(bundleTpe.aw.bits.len)) + val awsize = dpi("awsize", Input(bundleTpe.aw.bits.size)) + val awburst = dpi("awburst", Input(bundleTpe.aw.bits.burst)) + val awvalid = dpi("awvalid", Input(bundleTpe.aw.valid)) + val awready = dpi("awready", Output(bundleTpe.aw.ready)) + + // w channel + val wdata = dpi("wdata", Input(bundleTpe.w.bits.data)) + val wlast = dpi("wlast", Input(bundleTpe.w.bits.last)) + val wstrb = dpi("wstrb", Input(bundleTpe.w.bits.strb)) + val wvalid = dpi("wvalid", Input(bundleTpe.w.valid)) + val wready = dpi("wready", Output(bundleTpe.w.ready)) + + // b dpi + val bid = dpi("bid", Output(bundleTpe.b.bits.id)) + val bresp = dpi("bresp", Output(bundleTpe.b.bits.resp)) + val bvalid = dpi("bvalid", Output(bundleTpe.b.valid)) + val bready = dpi("bready", Input(bundleTpe.b.ready)) + + }) + dpiGen.clock.ref := clock.asBool + dpiGen.reset.ref := reset + + // ar connect + dpiGen.arid.ref := axi4Bundle.ar.bits.id + dpiGen.araddr.ref := axi4Bundle.ar.bits.addr + dpiGen.arlen.ref := axi4Bundle.ar.bits.len + dpiGen.arsize.ref := axi4Bundle.ar.bits.size + dpiGen.arburst.ref := axi4Bundle.ar.bits.burst + dpiGen.arvalid.ref := axi4Bundle.ar.valid + axi4Bundle.ar.ready := dpiGen.arready.ref + + // r connect + axi4Bundle.r.bits.id := dpiGen.rid.ref + axi4Bundle.r.bits.data := dpiGen.rdata.ref + axi4Bundle.r.bits.last := dpiGen.rlast.ref + axi4Bundle.r.bits.resp := dpiGen.rresp.ref + axi4Bundle.r.valid := dpiGen.rvalid.ref + dpiGen.rready.ref := axi4Bundle.r.ready + + // aw connect + dpiGen.awid.ref := axi4Bundle.aw.bits.id + dpiGen.awaddr.ref := axi4Bundle.aw.bits.addr + dpiGen.awlen.ref := axi4Bundle.aw.bits.len + dpiGen.awsize.ref := axi4Bundle.aw.bits.size + dpiGen.awburst.ref := axi4Bundle.aw.bits.burst + dpiGen.awvalid.ref := axi4Bundle.aw.valid + axi4Bundle.aw.ready := dpiGen.awready.ref + + // w connect + dpiGen.wdata.ref := axi4Bundle.w.bits.data + dpiGen.wlast.ref := axi4Bundle.w.bits.last + dpiGen.wstrb.ref := axi4Bundle.w.bits.strb + dpiGen.wvalid.ref := axi4Bundle.w.valid + axi4Bundle.w.ready := dpiGen.wready.ref + + // b connect + axi4Bundle.b.bits.id := dpiGen.bid.ref + axi4Bundle.b.bits.resp := dpiGen.bresp.ref + axi4Bundle.b.valid := dpiGen.bvalid.ref + dpiGen.bready.ref := axi4Bundle.b.ready + } +} diff --git a/tests/src/Main.scala b/tests/src/Main.scala new file mode 100644 index 0000000000..c7b6bc4172 --- /dev/null +++ b/tests/src/Main.scala @@ -0,0 +1,57 @@ +package org.chipsalliance.rocketchip.internal.tests + +import chisel3.RawModule +import chisel3.stage.ChiselGeneratorAnnotation +import chisel3.stage.phases.{Convert, Elaborate} +import firrtl.AnnotationSeq +import firrtl.options.TargetDirAnnotation +import freechips.rocketchip.diplomacy.LazyModule +import freechips.rocketchip.system.ExampleRocketSystem +import org.chipsalliance.cde.config.{Config, Parameters} +import mainargs._ + +object Main { + @main def elaborate( + @arg(name = "dir", doc = "output directory") dir: String, + @arg(name = "top", doc = "the top in ExampleRocketSystem type") top: String, + @arg(name = "config", doc = "CDE configs") config: Seq[String] + ) = { + var topName: String = null + val gen = () => + Class + .forName(top) + .getConstructor(classOf[Parameters]) + .newInstance(new Config(config.foldRight(Parameters.empty) { + case (currentName, config) => + val currentConfig = Class.forName(currentName).newInstance.asInstanceOf[Config] + currentConfig ++ config + })) match { + case lm: ExampleRocketSystem => lm + } + + val annos = Seq( + new Elaborate, + new Convert + ).foldLeft( + Seq( + TargetDirAnnotation(dir), + ChiselGeneratorAnnotation(() => new Testbench(gen())) + ): AnnotationSeq + ) { case (annos, phase) => phase.transform(annos) } + .flatMap { + case firrtl.stage.FirrtlCircuitAnnotation(circuit) => + topName = circuit.main + os.write(os.Path(dir) / s"${circuit.main}.fir", circuit.serialize) + None + case _: chisel3.stage.ChiselCircuitAnnotation => None + case _: chisel3.stage.DesignAnnotation[_] => None + case a => Some(a) + } + os.write(os.Path(dir) / s"$topName.anno.json", firrtl.annotations.JsonProtocol.serialize(annos)) + freechips.rocketchip.util.ElaborationArtefacts.files.foreach { + case (ext, contents) => os.write.over(os.Path(dir) / s"${config.mkString("_")}.${ext}", contents()) + } + } + + def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args) +} diff --git a/tests/src/Testbench.scala b/tests/src/Testbench.scala new file mode 100644 index 0000000000..8c8a7c9645 --- /dev/null +++ b/tests/src/Testbench.scala @@ -0,0 +1,47 @@ +package org.chipsalliance.rocketchip.internal.tests + +import chisel3._ +import chisel3.probe._ +import freechips.rocketchip.devices.debug.Debug +import freechips.rocketchip.diplomacy.LazyModule +import freechips.rocketchip.subsystem.ExtMem +import freechips.rocketchip.system.ExampleRocketSystem +import freechips.rocketchip.util.AsyncResetReg +import org.chipsalliance.cde.config.Parameters +import org.chipsalliance.rocketchip.internal.tests.dpi._ + +class Testbench(lm: ExampleRocketSystem) extends RawModule { + val dpiClockGen = Module(new ClockGen(ClockGenParameter(2))) + val clock = read(dpiClockGen.clock) + val reset = read(dpiClockGen.reset) + val dpiInit = Module(new InitCosim) + val dpiDumpWave = Module(new DumpWave) + val dpiFinish = Module(new Finish) + val dpiPlusArg = Module(new PlusArgVal) + + val ldut = LazyModule(lm) + implicit val p: Parameters = lm.p + + withClockAndReset(clock.asClock, reset) { + val dut = Module(ldut.module) + // Allow the debug ndreset to reset the dut, but not until the initial reset has completed + dut.reset := (reset.asBool | ldut.debug.map { debug => AsyncResetReg(debug.ndreset) }.getOrElse(false.B)).asBool + Debug.tieoffDebug(ldut.debug, ldut.resetctrl, Some(ldut.psd)) + dut.dontTouchPorts() + ldut.mem_axi4 + .zip(ldut.memAXI4Node.in) + .map { + case (io, (_, edge)) => + val mem = + LazyModule(new LazyAXI4MemBFM(edge, base = p(ExtMem).get.master.base, size = p(ExtMem).get.master.size)) + Module(mem.module).suggestName("mem") + mem.io_axi4.head <> io + mem + } + .toSeq + // tieoff + ldut.mmio_axi4 <> DontCare + ldut.l2_frontend_bus_axi4 <> DontCare + ldut.module.tieOffInterrupts() + } +} diff --git a/tests/src/dpi/ClockGen.scala b/tests/src/dpi/ClockGen.scala new file mode 100644 index 0000000000..97ee05c21c --- /dev/null +++ b/tests/src/dpi/ClockGen.scala @@ -0,0 +1,23 @@ +package org.chipsalliance.rocketchip.internal.tests.dpi + +import chisel3._ +import chisel3.experimental.ExtModule +import chisel3.probe._ +import chisel3.util.HasExtModuleInline + +case class ClockGenParameter(clockRate: Int) + +class ClockGen(val parameter: ClockGenParameter) extends ExtModule with HasExtModuleInline with HasExtModuleDefine { + setInline( + s"$desiredName.sv", + s"""module $desiredName; + | reg clock = 1'b0; + | always #(${parameter.clockRate}) clock = ~clock; + | reg reset = 1'b1; + | initial #(${2 * parameter.clockRate + 1}) reset = 0; + |endmodule + |""".stripMargin + ) + val clock = define(RWProbe(Bool()), Seq("ClockGen", "ClockGen", "clock")) + val reset = define(RWProbe(Bool()), Seq("ClockGen", "ClockGen", "reset")) +} diff --git a/tests/src/dpi/DumpWave.scala b/tests/src/dpi/DumpWave.scala new file mode 100644 index 0000000000..1220f3d370 --- /dev/null +++ b/tests/src/dpi/DumpWave.scala @@ -0,0 +1,15 @@ +package org.chipsalliance.rocketchip.internal.tests.dpi + +import chisel3._ + +class DumpWave extends DPIModule { + val isImport: Boolean = false + + // TODO: think about `chisel3.properties.Property`? + override val exportBody = s""" + |function $desiredName(input string file); + | $$dumpfile(file); + | $$dumpvars(0); + |endfunction; + |""".stripMargin +} diff --git a/tests/src/dpi/Finish.scala b/tests/src/dpi/Finish.scala new file mode 100644 index 0000000000..179fe7fccc --- /dev/null +++ b/tests/src/dpi/Finish.scala @@ -0,0 +1,12 @@ +package org.chipsalliance.rocketchip.internal.tests.dpi + +import chisel3._ + +class Finish extends DPIModule { + val isImport: Boolean = false + override val exportBody = s""" + |function $desiredName(); + | $$finish; + |endfunction; + |""".stripMargin +} diff --git a/tests/src/dpi/InitCosim.scala b/tests/src/dpi/InitCosim.scala new file mode 100644 index 0000000000..1d5de6d790 --- /dev/null +++ b/tests/src/dpi/InitCosim.scala @@ -0,0 +1,8 @@ +package org.chipsalliance.rocketchip.internal.tests.dpi + +import chisel3._ + +class InitCosim extends DPIModule { + val isImport: Boolean = true + override val trigger: String = s"initial" +} diff --git a/tests/src/dpi/PlusArgVal.scala b/tests/src/dpi/PlusArgVal.scala new file mode 100644 index 0000000000..ab04605a1d --- /dev/null +++ b/tests/src/dpi/PlusArgVal.scala @@ -0,0 +1,15 @@ +package org.chipsalliance.rocketchip.internal.tests.dpi + +import chisel3._ + +class PlusArgVal extends DPIModule { + val isImport: Boolean = false + + override val exportBody = s""" + |function automatic string $desiredName(input string param); + | string val = ""; + | if (!$$value$$plusargs(param, val)) val = ""; + | return val; + |endfunction; + |""".stripMargin +} diff --git a/tests/src/hack/DPIModule.scala b/tests/src/hack/DPIModule.scala new file mode 100644 index 0000000000..f1a56044b6 --- /dev/null +++ b/tests/src/hack/DPIModule.scala @@ -0,0 +1,103 @@ +// TODO: this is DIRTY, the future plan for this is provide a DPIIntModule with parameters: trigger, guard, function signature. +package chisel3 + +import chisel3.experimental.ExtModule +import chisel3.internal.firrtl.{KnownWidth, UnknownWidth} +import chisel3.util.HasExtModuleInline + +import scala.collection.mutable.ArrayBuffer + +case class DPIElement[T <: Element](name: String, output: Boolean, data: T) + +case class DPIReference[T <: Element](name: String, ref: T) + +abstract class DPIModule extends ExtModule with HasExtModuleInline { + + // C Style + override def desiredName: String = "[A-Z\\d]".r.replaceAllIn( + super.desiredName, + { m => + (if (m.end(0) == 1) "" else "_") + m.group(0).toLowerCase() + } + ) + + def dpiTrigger[T <: Element](name: String, data: T) = bind(name, false, Input(data.cloneType)) + + def dpi[T <: Element](name: String, data: T) = bind(name, true, data) + + val isImport: Boolean + val references: ArrayBuffer[DPIElement[_]] = scala.collection.mutable.ArrayBuffer.empty[DPIElement[_]] + val dpiReferences: ArrayBuffer[DPIElement[_]] = scala.collection.mutable.ArrayBuffer.empty[DPIElement[_]] + + def bind[T <: Element](name: String, isDPIArg: Boolean, data: T) = { + val ref = IO(data).suggestName(name) + + val ele = DPIElement( + name, + chisel3.reflect.DataMirror.directionOf(ref) match { + case ActualDirection.Empty => throw new Exception("no direction") + case ActualDirection.Unspecified => throw new Exception("no direction") + case ActualDirection.Output => true + case ActualDirection.Input => false + case ActualDirection.Bidirectional(dir) => throw new Exception("unknown direction") + }, + ref + ) + require(!references.exists(ele => ele.name == name), s"$name already added.") + references += ele + if (isDPIArg) { + dpiReferences += ele + } + DPIReference(name, ref) + } + + val trigger: String = "" + val guard: String = "" + val exportBody: String = "" + // Magic to execute post-hook + private[chisel3] override def generateComponent() = { + // return binding function and probe signals + val localDefinition = "(" + references.map { + case DPIElement(name, _, element) => + val output = chisel3.reflect.DataMirror.directionOf(element) match { + case ActualDirection.Empty => throw new Exception("no direction") + case ActualDirection.Unspecified => throw new Exception("no direction") + case ActualDirection.Output => true + case ActualDirection.Input => false + case ActualDirection.Bidirectional(dir) => throw new Exception("unknown direction") + } + val width = chisel3.reflect.DataMirror.widthOf(element) match { + case UnknownWidth() => throw new Exception(s"$desiredName.$name width unknown") + case KnownWidth(value) => value + } + val tpe = if (width != 1) s"bit[${width - 1}:0] " else "" + s"${if (output) "output" else "input"} $tpe$name" + }.mkString(", ") + ")" + + val dpiArg = dpiReferences.map { + case DPIElement(name, output, element) => + val direction = if (output) "output " else "input " + val width = chisel3.reflect.DataMirror.widthOf(element) match { + case UnknownWidth() => throw new Exception(s"$desiredName.$name width unknown") + case KnownWidth(value) => value + } + val tpe = if (width != 1) s"bit[${width - 1}:0] " else "" + s"$direction$tpe$name" + }.mkString(", ") + + setInline( + s"$desiredName.sv", + s"""module $desiredName$localDefinition; + |${if (isImport) s"""import "DPI-C" function void $desiredName($dpiArg);""" + else s"""export "DPI-C" function $desiredName;"""} + |${if (isImport) s"""$trigger ${if (guard.nonEmpty) s"if($guard)" else ""} $desiredName(${dpiReferences + .map(_.name) + .mkString(", ")});""" + else ""} + |${if (!isImport) exportBody else ""} + |endmodule + |""".stripMargin.lines().filter(_.nonEmpty).toArray.mkString("\n") + ) + super.generateComponent() + } +} diff --git a/tests/src/hack/HasExtModuleDefine.scala b/tests/src/hack/HasExtModuleDefine.scala new file mode 100644 index 0000000000..5964968423 --- /dev/null +++ b/tests/src/hack/HasExtModuleDefine.scala @@ -0,0 +1,34 @@ +package chisel3 + +/** + * The mixin to define the probe signal for BlackBox. + */ +trait HasExtModuleDefine extends chisel3.util.HasExtModuleInline { this: chisel3.experimental.ExtModule => + + /** Create a ProbeType for external sources. + * @param tpe is the Chisel type of signal. + * @param path is the internal path of the signal see [[https://github.com/chipsalliance/firrtl-spec/blob/main/abi.md]] + */ + def define[T <: chisel3.Element](tpe: T, path: Seq[String]): T = { + setInline( + s"ref_${path.mkString("_")}.sv", + s"`define ref_${path.mkString("_")} ${path.last}" + ) + // it's not IO, but BlackBox can only bind IO + val io = IO(tpe).suggestName(path.last) + require(chisel3.reflect.DataMirror.hasProbeTypeModifier(io), s"$tpe for $path should be a ProbeType") + io + } + + def defineVec[A <: chisel3.Vec[_]](tpe: A, path: Seq[String]): A = { + setInline( + s"ref_${path.mkString("_")}.sv", + (Seq(s"`define ref_${path.mkString("_")} ${path.last}") + ++ Seq.tabulate(tpe.size)(i => s"`define ref_${path.mkString("_")}_$i ${path.last}[$i]")).mkString("\n") + ) + // it's not IO, but BlackBox can only bind IO + val io = IO(tpe).suggestName(path.last) + require(chisel3.reflect.DataMirror.hasProbeTypeModifier(io), s"$tpe for $path should be a ProbeType") + io + } +}