diff --git a/.gitattributes b/.gitattributes index 097f9f98d..6abd78759 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,9 @@ # # https://help.github.com/articles/dealing-with-line-endings/ # -# Linux start script should use lf +# Linux scripts should use lf /gradlew text eol=lf +*.sh text eol=lf # These are Windows script files and should use crlf *.bat text eol=crlf diff --git a/sys/risc-v/mia/rv_1stage.vadl b/sys/risc-v/mia/rv_1stage.vadl new file mode 100644 index 000000000..0ec871736 --- /dev/null +++ b/sys/risc-v/mia/rv_1stage.vadl @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: Apache-2.0 + +import "../rv32i"::RV32I +import "../rv32im"::RV32IM + +import "../rv64i"::RV64I +import "../rv64im"::RV64IM + +model Isa() : Id = {RV32I} // invoke vadl -m "Isa=RV64I" for 64 bit arch + +/** + * Single stage MiA description. + * + * Placing the whole instruction behavior in a single pipeline stage may require the stage to + * take more than one cycle for execution. In this case an implementation could share resources + * between the execution steps (which our hardware generation currently does not do). + */ +micro architecture SingleStage implements $Isa = { + + stage ISS -> ( ) = { + let instr = decode( fetchNext ) in // fetch next instruction from memory and decode + instr.write + } +} diff --git a/sys/risc-v/mia/rv_3stage.vadl b/sys/risc-v/mia/rv_3stage.vadl new file mode 100644 index 000000000..6aacec6e3 --- /dev/null +++ b/sys/risc-v/mia/rv_3stage.vadl @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: Apache-2.0 + +import "../rv32i"::RV32I +import "../rv32im"::RV32IM + +import "../rv64i"::RV64I +import "../rv64im"::RV64IM + +model Isa() : Id = {RV32I} // invoke vadl -m "Isa=RV64I" for 64 bit arch + +/** + * Three stage MiA description. + * + * This models a pipeline with stages Instruction Fetch, Instruction Decode and Execute following + * the design of the Wildcat educational processor. + * + * Schoeberl, M. (2025). Wildcat: Educational RISC-V Microprocessors. arXiv. + * http://arxiv.org/abs/2502.20197 + */ +micro architecture ThreeStage implements $Isa = { + + stage IF -> ( fr : FetchResult ) = { + fr := fetchNext // fetch next instruction from memory (or cache) + } + + stage ID -> ( ir : Instruction ) = { + let instr = decode( IF.fr ) in { // decode the fetch packet, gives an Instruction +// if ( instr.unknown ) then +// raise invalidInstruction // raise an invalid instruction exception + instr.address( @X ) // compute addresses to access register file X + instr.read( @X ) // read from the X register file + instr.read( @PC ) // read from the PC + ir := instr // stage output is the Instruction ir + } + } + + stage EX -> ( ir : Instruction ) = { + let instr = ID.ir in { + instr.write // write PC + ir := instr + } + } +} diff --git a/sys/risc-v/rv64imFive.vadl b/sys/risc-v/mia/rv_5stage.vadl similarity index 67% rename from sys/risc-v/rv64imFive.vadl rename to sys/risc-v/mia/rv_5stage.vadl index 6124219e1..607b8018d 100644 --- a/sys/risc-v/rv64imFive.vadl +++ b/sys/risc-v/mia/rv_5stage.vadl @@ -1,21 +1,27 @@ // SPDX-FileCopyrightText : © 2024 TU Wien // SPDX-License-Identifier: Apache-2.0 -// RISC-V 32 I instruction set +// RISC-V five stage pipeline -import rv64im::{RV64IM} +import "../rv32i"::RV32I +import "../rv32im"::RV32IM -micro architecture FiveStage implements RV64IM = { +import "../rv64i"::RV64I +import "../rv64im"::RV64IM + +model Isa() : Id = {RV32I} // invoke vadl -m "Isa=RV64I" for 64 bit arch + +micro architecture FiveStage implements $Isa = { stage FETCH -> ( fr : FetchResult ) = { - fr := fetchNext // fetch next packet from memory (or cache) + fr := fetchNext // fetch next instruction from memory (or cache) } stage DECODE -> ( ir : Instruction ) = { let instr = decode( FETCH. fr ) in { // decode the fetch packet, gives an Instruction - if ( instr.unknown ) then - raise invalidInstruction // raise an invalid instruction exception - instr.address( @X ) // output computed address (X + offset ) to memory +// if ( instr.unknown ) then +// raise invalidInstruction // raise an invalid instruction exception + instr.address( @X ) // compute addresses to access register file X instr.read( @X ) // read from the X register file instr.read( @PC ) // read from the PC ir := instr // stage output is the Instruction ir diff --git a/sys/risc-v/rv64imFiveMul.vadl b/sys/risc-v/mia/rv_5stage_mul.vadl similarity index 90% rename from sys/risc-v/rv64imFiveMul.vadl rename to sys/risc-v/mia/rv_5stage_mul.vadl index 5cf4f118a..f5a1cf4e1 100644 --- a/sys/risc-v/rv64imFiveMul.vadl +++ b/sys/risc-v/mia/rv_5stage_mul.vadl @@ -1,11 +1,15 @@ // SPDX-FileCopyrightText : © 2024 TU Wien // SPDX-License-Identifier: Apache-2.0 -// RISC-V 64 five stage pipeline with longer mul/div pipeline +// RISC-V five stage pipeline with longer mul/div pipeline -import rv64im::{RV64IM} +import "../rv32im"::RV32IM -micro architecture FiveStage implements RV64IM = { +import "../rv64im"::RV64IM + +model Isa() : Id = {RV32IM} // invoke vadl -m "Isa=RV64IM" for 64 bit arch + +micro architecture FiveStageMul implements $Isa = { operation AddOps = {ADD, SUB, AND, OR, XOR, SLT, SLTU, SLL, SRL, SRA} operation ImmOps = {ADDI, ANDI, ORI, XORI, SLTI, SLTIU, AUIPC, LUI} diff --git a/vadl-cli/main/vadl/cli/RtlCommand.java b/vadl-cli/main/vadl/cli/RtlCommand.java index 03a0a79e9..0587952a1 100644 --- a/vadl-cli/main/vadl/cli/RtlCommand.java +++ b/vadl-cli/main/vadl/cli/RtlCommand.java @@ -37,12 +37,6 @@ ) public class RtlCommand extends BaseCommand { - @CommandLine.Option(names = {"--dummy-mia"}, - scope = INHERIT, - description = "Select a dummy MiA: ${COMPLETION-CANDIDATES} (stages in pipeline)", - defaultValue = "five") - RtlConfiguration.DummyMia dummyMia = RtlConfiguration.DummyMia.five; - @CommandLine.Option(names = {"--memory"}, scope = INHERIT, description = "Configure external memory interface: ${COMPLETION-CANDIDATES}", @@ -84,6 +78,12 @@ public class RtlCommand extends BaseCommand { defaultValue = "false") boolean keepSignals = false; + @CommandLine.Option(names = {"--rvfi"}, + scope = INHERIT, + description = "Emit outputs for the RISC-V Formal Interface.", + defaultValue = "false") + boolean emitRVFI = false; + @CommandLine.Option(names = {"--dry-run"}, scope = INHERIT, description = "Don't emit generated files.") @@ -92,13 +92,13 @@ public class RtlCommand extends BaseCommand { @Override PassOrder passOrder(GeneralConfiguration configuration) throws IOException { var rtlConfig = new RtlConfiguration(configuration); - rtlConfig.setDummyMia(dummyMia); rtlConfig.setMemory(memory); rtlConfig.setResetVector(resetVector); rtlConfig.setKeepSignals(keepSignals); rtlConfig.setScalaPackageAndDirs(scalaPackage); rtlConfig.setTopModule(topModule); rtlConfig.setProjectName(projectName); + rtlConfig.setEmitRVFI(emitRVFI); rtlConfig.setDryRun(dryRun); return PassOrders.rtl(rtlConfig); } diff --git a/vadl/main/resources/templates/rtl/Makefile b/vadl/main/resources/templates/rtl/Makefile index 6f5175b95..db5ce00be 100644 --- a/vadl/main/resources/templates/rtl/Makefile +++ b/vadl/main/resources/templates/rtl/Makefile @@ -1,29 +1,36 @@ # Makefile for [(${projectName})] -SHELL=/bin/bash +# Preserve test exit code when piping +SHELL=/bin/bash -o pipefail + +.PHONY: test test: - mkdir -p build - touch build/[(${topModule})]-riscv-tests.log - set -o pipefail # Preserve test exit code when piping to tee - sbt test 2>&1 | tee build/[(${topModule})]-riscv-tests.log - -riscv-tests: - git clone https://github.com/riscv/riscv-tests - cd riscv-tests && \ - git checkout b5ba87097c42aa41c56657e0ae049c2996e8d8d8 && \ - git submodule update --init --recursive - # remove multicore disable because mhartid is not implemented - sed -ie '/#define RISCV_MULTICORE_DISABLE/{n;N;d}' riscv-tests/env/p/riscv_test.h - sed -ie '/csrr a0, mhartid;/d' riscv-tests/env/p/riscv_test.h -[# th:unless="${hasEcall}"] # patch ecall to also write test result to tohost address - sed -i '/ecall/i \ - la a4, tohost; \\\ - sw TESTNUM, 0(a4); \\' riscv-tests/env/p/riscv_test.h -[/] cd riscv-tests && \ - autoconf && \ - ./configure --prefix=/usr && \ - RISCV_GCC_OPTS="-static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles -I/usr/include" make isa + sbt "testOnly ElfSimTest" + +# Elfsim with C++ memory model +SIM_BUILD=build/elfsim +SIM_WORK=$(SIM_BUILD)/workdir-verilator +SIM=$(SIM_WORK)/simulation +SIM_LOG=$(SIM_BUILD)/simulation.log +SIM_RPT=$(SIM_BUILD)/simulation.rpt + +TESTS?=$(filter-out %.dump,$(wildcard /riscv-tests/isa/[(${#strings.substring(isaName, 0, 4).toLowerCase()})]ui-p-*)) +TEST_OUT=$(SIM_BUILD)/tests + +$(SIM): + sbt "test" + +$(TEST_OUT): $(SIM) + mkdir -p $(TEST_OUT) + +$(SIM_RPT): $(SIM) $(TESTS) $(TEST_OUT) + (for t in $(TESTS); do echo "test $$t"; \ + echo -e "R a\nS 1 0\nR a\nS 1 1\nT 0 0,1-5*1\nS 1 0\nR 0\nW 1\nT 0 0,1-5*ffff\nD" \ + | ($(SIM) "+elf=$$t" 2>&1 || true) | tee $(TEST_OUT)/`basename $$t`.log ; \ + mv trace.vcd $(TEST_OUT)/`basename $$t`.vcd; done) \ + | tee $(SIM_LOG) + grep -E "^(test|^RVTEST_)" $(SIM_LOG) | tee $(SIM_RPT) # scalafmt download and format target ifeq ($(OS),Windows_NT) diff --git a/vadl/main/resources/templates/rtl/cpp/SimMem.cpp b/vadl/main/resources/templates/rtl/cpp/SimMem.cpp new file mode 100644 index 000000000..211f21a0b --- /dev/null +++ b/vadl/main/resources/templates/rtl/cpp/SimMem.cpp @@ -0,0 +1,215 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +using Data = uint8_t; +using Address = uint64_t; + +class SimMem { + + struct Segment { + public: + Address start; + Address end; // exclusive (last address + 1) + Data *data; + + Segment(Address start, Address end, Data *data) + : start(start), end(end), data(data) { + } + + bool match(Address address) { + return (address >= start && address < end); + } + + Data read(Address address) { + return data[index(address)]; + } + + void write(Address address, Data data) { + this->data[index(address)] = data; + } + + private: + Address index(Address address) { + return (address - start); + } + }; + + public: + bool loadElf(const char *file); + + void map(Address address, size_t bufLen, Data *buf, size_t len); + + Data read(Address address); + void write(Address address, Data data); + + Address getEntry(); + Address getSymbol(std::string symbol); + + private: + Address entry; + std::map symbols; + std::vector segments; + +}; + +// Constructor and ELF Loader + +#define IS_ELF(hdr) \ + ((hdr).e_ident[0] == 0x7f && (hdr).e_ident[1] == 'E' && \ + (hdr).e_ident[2] == 'L' && (hdr).e_ident[3] == 'F') + +#define IS_ELF32(hdr) (IS_ELF(hdr) && (hdr).e_ident[4] == 1) +#define IS_ELF64(hdr) (IS_ELF(hdr) && (hdr).e_ident[4] == 2) + +#define LOAD_ELF(ehdr_t, phdr_t, shdr_t, sym_t) do { \ + ehdr_t* eh = (ehdr_t*)buf; \ + phdr_t* ph = (phdr_t*)(buf + eh->e_phoff); \ + this->entry = eh->e_entry; \ + assert(size >= eh->e_phoff + eh->e_phnum * sizeof(*ph)); \ + for (unsigned i = 0; i < eh->e_phnum; i++) { \ + if(ph[i].p_type == PT_LOAD && ph[i].p_memsz) { \ + if (ph[i].p_filesz) \ + assert(size >= ph[i].p_offset + ph[i].p_filesz); \ + this->map(ph[i].p_paddr, \ + ph[i].p_filesz * (sizeof(uint8_t)+sizeof(Data)-1)/sizeof(Data), \ + (Data*)(buf + ph[i].p_offset), \ + ph[i].p_memsz * (sizeof(uint8_t)+sizeof(Data)-1)/sizeof(Data)); \ + } \ + } \ + shdr_t* sh = (shdr_t*)(buf + eh->e_shoff); \ + assert(size >= eh->e_shoff + eh->e_shnum * sizeof(*sh)); \ + assert(eh->e_shstrndx < eh->e_shnum); \ + assert(size >= sh[eh->e_shstrndx].sh_offset + sh[eh->e_shstrndx].sh_size); \ + char *shstrtab = buf + sh[eh->e_shstrndx].sh_offset; \ + signed strtabidx = eh->e_shnum, symtabidx = eh->e_shnum; \ + for (unsigned i = 0; i < eh->e_shnum; i++) { \ + if (strcmp(shstrtab + sh[i].sh_name, ".strtab") == 0) strtabidx = i; \ + if (strcmp(shstrtab + sh[i].sh_name, ".symtab") == 0) symtabidx = i; \ + } \ + if (strtabidx != eh->e_shnum && symtabidx != eh->e_shnum) { \ + char* strtab = buf + sh[strtabidx].sh_offset; \ + sym_t* sym = (sym_t*)(buf + sh[symtabidx].sh_offset); \ + for (unsigned i = 0; i < sh[symtabidx].sh_size / sizeof(sym_t); i++) { \ + this->symbols[strtab + sym[i].st_name] = sym[i].st_value; \ + } \ + } \ +} while(0) + +bool SimMem::loadElf(const char *file) { + int fd = open(file, O_RDONLY); + struct stat s; + if (fd == -1 || fstat(fd, &s) < 0) { + std::cerr << "Could not open SimMem file: " << file << std::endl; + return false; + } + size_t size = s.st_size; + char* buf = (char*)mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (buf == MAP_FAILED) { + std::cerr << "Could not mmap SimMem file: " << file << std::endl; + close(fd); + return false; + } + close(fd); + + const Elf64_Ehdr* eh64 = (const Elf64_Ehdr*)buf; + if (IS_ELF32(*eh64)) { + LOAD_ELF(Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Sym); + } else if (IS_ELF64(*eh64)) { + LOAD_ELF(Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Sym); + } else { + std::cerr << "Could not load SimMem file: " << file << std::endl; + munmap(buf, size); + return false; + } + + munmap(buf, size); + return true; +} + +// Create segments + +void SimMem::map(Address address, size_t bufLen, Data *buf, size_t len) { + Data *mem = new Data[len]; + memcpy(mem, buf, bufLen); + memset(mem + bufLen, 0, len - bufLen); + this->segments.push_back(Segment(address, address + len, mem)); +} + +// Read, Write + +Data SimMem::read(Address address) { + for (auto seg : this->segments) { + if (seg.match(address)) { + return seg.read(address); + } + } + return 0; +} + +void SimMem::write(Address address, Data data) { + for (auto seg : this->segments) { + if (seg.match(address)) { + return seg.write(address, data); + } + } +} + +// Get Symbols + +Address SimMem::getEntry() { + return entry; +} + +Address SimMem::getSymbol(std::string symbol) { + return symbols[symbol]; +} + +// DPI calls + +std::map mems; +std::mutex mems_mutex; + +extern "C" void *simmem_init(char *name, char *file) { + mems_mutex.lock(); + SimMem *mem; + auto it = mems.find(name); + if (it != mems.end()) { + mem = it->second; + } else { + mem = new SimMem(); + if (file[0] != '\0' && !mem->loadElf(file)) { + std::cerr << "Could not load SimMem file: " << file << std::endl; + return nullptr; + } + mems[name] = mem; + } + mems_mutex.unlock(); + return mem; +} + +extern "C" Data simmem_read(void *mem, Address address) { + return ((SimMem*)mem)->read(static_cast
(address)); +} + +extern "C" void simmem_write(void *mem, Address address, Data data) { + ((SimMem*)mem)->write(static_cast
(address), static_cast(data)); +} + +extern "C" Address simmem_entry(void *mem) { + return ((SimMem*)mem)->getEntry(); +} + +extern "C" Address simmem_symbol(void *mem, char *symbol) { + return ((SimMem*)mem)->getSymbol(symbol); +} diff --git a/vadl/main/resources/templates/rtl/CoreEmit.scala b/vadl/main/resources/templates/rtl/scala/CoreEmit.scala similarity index 100% rename from vadl/main/resources/templates/rtl/CoreEmit.scala rename to vadl/main/resources/templates/rtl/scala/CoreEmit.scala diff --git a/vadl/main/resources/templates/rtl/CoreTest.scala b/vadl/main/resources/templates/rtl/scala/CoreTest.scala similarity index 97% rename from vadl/main/resources/templates/rtl/CoreTest.scala rename to vadl/main/resources/templates/rtl/scala/CoreTest.scala index f70004d5e..bdc80f90a 100644 --- a/vadl/main/resources/templates/rtl/CoreTest.scala +++ b/vadl/main/resources/templates/rtl/scala/CoreTest.scala @@ -4,6 +4,7 @@ import chisel3.simulator._ import chisel3.testing._ import net.fornwall.jelf.{ElfFile, ElfSegment} +import org.scalatest.Ignore import org.scalatest.funspec.AnyFunSpec import svsim.verilator.Backend.CompilationSettings.TraceKind.Vcd @@ -15,6 +16,7 @@ import scala.util.Random /** * Simulate the core connected to a memory, load an ELF file to memory. */ +@Ignore class CoreTest extends AnyFunSpec with ChiselSim { implicit val testingDirectory: HasTestingDirectory = new HasTestingDirectory { @@ -138,7 +140,8 @@ class CoreTest extends AnyFunSpec with ChiselSim { if (en) { val addr = wr.address.peekValue().asBigInt for (i <- wr.data.indices) { - write(addr + i, wr.data(i).peek().asUInt) + if (i < wr.words.peekValue().asBigInt) + write(addr + i, wr.data(i).peek().asUInt) } if (tohost.isDefined && addr.toLong.equals(tohost.get)) { val elems = wr.data.getElements.map(e => e.peek().asUInt) @@ -173,7 +176,7 @@ class CoreTest extends AnyFunSpec with ChiselSim { dut.clock.step() cycles += 1 - if (cycles > 2_000) { + if (cycles > 10_000) { println("RVTEST_TIMEOUT") timeout += file.getName run = false diff --git a/vadl/main/resources/templates/rtl/scala/ElfSim.scala b/vadl/main/resources/templates/rtl/scala/ElfSim.scala new file mode 100644 index 000000000..6ee460ee7 --- /dev/null +++ b/vadl/main/resources/templates/rtl/scala/ElfSim.scala @@ -0,0 +1,263 @@ +[# th:if="${package != ''}"]package [(${package})] + +[/]import chisel3._ +import chisel3.experimental._ +import chisel3.util._ +import chisel3.simulator._ +import chisel3.testing._ +import svsim._ + +import VADL._ + +import org.scalatest.BeforeAndAfterAll +import org.scalatest.funspec.AnyFunSpec + +import java.nio.file.Path + +class ElfSimBase(directory: String) extends AnyFunSpec with ChiselSim { + + implicit val testingDirectory: HasTestingDirectory = new HasTestingDirectory { + override def getDirectory: Path = svsim.Workspace.getProjectRootOrCwd + .resolve("build") + .resolve(directory) + } + + implicit val hasSimulator: HasSimulator = HasSimulator.simulators.verilator( + verilatorSettings = svsim.verilator.Backend.CompilationSettings( + traceStyle = Some( + svsim.verilator.Backend.CompilationSettings.TraceStyle( + kind = svsim.verilator.Backend.CompilationSettings.TraceKind.Vcd + ) + ) + ), + compilationSettings = CommonCompilationSettings.default.copy( + simulationSettings = CommonSimulationSettings.default.copy( + plusArgs = Seq(new svsim.PlusArg("elf", Some(""))) + ) + ) + ) + + val simulator = hasSimulator.getSimulator + val workingDirectoryPrefix = simulator.workingDirectoryPrefix + val workingDirectoryTag = simulator.tag + val workingDirectory = testingDirectory.getDirectory + .resolve(s"$workingDirectoryPrefix-$workingDirectoryTag") + val executable = workingDirectory.resolve("simulation").toString + + + // build ElfSim by running an empty simulation + def build(): Unit = { + simulate (new ElfSim[# th:inline="none"][[/][(${topModule})][# th:inline="none"]][/](new [(${topModule})], _.io, m => [# + th:if="${resetVector != null}"]Some(m.io.[(${resetVector})]_in)[/][# + th:if="${resetVector == null}"]None[/])) { dut => } + } + +} + +/** + * Build the ElfSim simulator. + */ +class ElfSimBuild extends ElfSimBase("elfsim") { + + it("builds") { + build() + } + +} + +/** + * Build the ElfSim simulator and run the riscv-tests on it. + */ +class ElfSimTest extends ElfSimBase("elfsimtest") with BeforeAndAfterAll { + + override def beforeAll(): Unit = { + build() + } + + // run riscv-tests + new java.io.File("/riscv-tests/isa").listFiles(_.getName.matches("[(${#strings.substring(isaName, 0, 4).toLowerCase()})]ui-p-[^.]*")) + .filter(file => !file.getName.contains("fence")) // not supported + .foreach(file => { + it(f"${file.getName}") { + val relFile = workingDirectory.toAbsolutePath.relativize(file.toPath.toAbsolutePath) + val settings = CommonSimulationSettings.default.copy( + plusArgs = Seq(new svsim.PlusArg("elf", Some(f"$relFile"))) + ) + + val command = Seq(executable) ++ settings.plusArgs.map(_.simulatorFlags) + + val process = new ProcessBuilder(command: _*) + .directory(workingDirectory.toFile) + .redirectErrorStream(true) + .start() + + val cycles = 10_000 + val trace = true + process.outputWriter.write( + s"""R a + |S 1 0 + |R a + |S 1 1 + |T 0 0,1-5*1 + |S 1 0 + |R 0${if (trace) "\nW 1" else ""} + |T 0 0,1-5*${cycles.toHexString} + |D\n""".stripMargin.replace("\r", "")) + process.outputWriter.flush() + + var success: Option[String] = None + var fail: Option[String] = None + process.inputReader.lines().forEach(line => { + if (line.contains("RVTEST_PASS")) { + success = Some(line) + } + if (line.contains("RVTEST_FAIL")) { + fail = Some(line) + } + }) + + process.waitFor() + + fail match { + case Some(line) => throw new Exception(s"$line") + case None => + } + success match { + case Some(line) => + case None => throw new Exception("timeout") + } + } + }) + +} + +/** + * ElfSim test bench that connects all memory ports to the SimMem, optionally reads the reset vector from it. + * + * @param module core module + * @param getIO function to get IO bundle from core module + * @param getResetVector function to get optional reset vector from core module + * @tparam T core module type + */ +class ElfSim[T <: Module](module: => T, getIO: T => Bundle, getResetVector: T => Option[Data]) extends Module { + + val core = Module(module) + val core_io = getIO(core) + + val addrWidth = core_io.getElements.map { + case rd: VADL.MemReadPort[?] => rd.address.getWidth + case wr: VADL.MemWritePort[?] => wr.address.getWidth + case _ => 0 + }.max + + val simSymb = Module(new SimMemSymbols("elf", addrWidth)) + simSymb.io.clock := clock + simSymb.io.reset := reset + + getResetVector(core) match { + case Some(d) => d := simSymb.io.entry + case _ => + } + + core_io.getElements.foreach { + case rd: VADL.MemReadPort[Bits] => + val simRead = Module(new SimMemRead("elf", rd)) + simRead.io.clock := clock + simRead.io.reset := reset + simRead.io.enable := rd.enable + simRead.io.address := rd.address + simRead.io.words := rd.words + for (i <- rd.data.indices) { + val w = rd.data(i).getWidth + rd.data(i) := simRead.io.data(w * (i + 1) - 1, w * i) + } + rd.valid := simRead.io.valid + when(rd.enable && rd.address === simSymb.io.fromhost) { + printf(cf"fromhost not implemented\n") + } + case wr: VADL.MemWritePort[Bits] => + val simWrite = Module(new SimMemWrite("elf", wr)) + simWrite.io.clock := clock + simWrite.io.reset := reset + simWrite.io.enable := wr.enable + simWrite.io.address := wr.address + simWrite.io.words := wr.words + simWrite.io.data := Cat(wr.data.reverse) + wr.valid := simWrite.io.valid + when(wr.enable && wr.address === simSymb.io.tohost) { + val value = Cat(wr.data.reverse) + printf(cf"tohost: $value\n") + when(value(0) === 1.U) { + val result = value >> 1 + when(result === 0.U) { + printf(cf"RVTEST_PASS\n") + }.otherwise { + printf(cf"RVTEST_FAIL $result\n") + } + stop() + } + } + case elem => dontTouch(elem) + } +} + +// SimMem black boxes that connect to the C++ simulation model of the memory + +abstract class SimMem[T <: Data](name: String, dataWidth: Int, addrWidth: Int, wordWidth: Int, + params: Map[String, Param] = Map.empty) + extends BlackBox(Map.newBuilder[String, Param] + .addOne("NAME" -> StringParam(name)) + .addOne("DATA_WIDTH" -> IntParam(dataWidth)) + .addOne("ADDR_WIDTH" -> IntParam(addrWidth)) + .addOne("WORD_WIDTH" -> IntParam(wordWidth)) + .addAll(params) + .result() + ) + with HasBlackBoxResource { + + val dataType = Bits(dataWidth.W) + + def getIO(dataOut: Boolean) = new Bundle { + val clock = Input(Clock()) + val reset = Input(Reset()) + + val enable = Input(Bool()) + val address = Input(Bits(addrWidth.W)) + val words = Input(Bits((log2Ceil(dataWidth/wordWidth+1)).W)) + val data = if (dataOut) Output(dataType) else Input(dataType) + val valid = Output(Bool()) + } + + addResource("/SimMem.sv") + addResource("/SimMem.cpp") + +} + +class SimMemRead[T <: Data](name: String, port: MemReadPort[T]) extends SimMem( + name, + port.data.getWidth, + port.address.getWidth, + port.data.getElements.map(_.getWidth).max +) { + val io = IO(getIO(true)) +} + +class SimMemWrite[T <: Data](name: String, port: MemWritePort[T]) extends SimMem( + name, + port.data.getWidth, + port.address.getWidth, + port.data.getElements.map(_.getWidth).max +) { + val io = IO(getIO(false)) +} + +class SimMemSymbols[T <: Data](name: String, addrWidth: Int) extends SimMem(name, 0 /* not used */, addrWidth, 0) { + val io = IO(new Bundle { + val clock = Input(Clock()) + val reset = Input(Reset()) + + val entry = Output(Bits(addrWidth.W)) + val fromhost = Output(Bits(addrWidth.W)) + val tohost = Output(Bits(addrWidth.W)) + }) +} diff --git a/vadl/main/resources/templates/rtl/Module.scala b/vadl/main/resources/templates/rtl/scala/Module.scala similarity index 100% rename from vadl/main/resources/templates/rtl/Module.scala rename to vadl/main/resources/templates/rtl/scala/Module.scala diff --git a/vadl/main/resources/templates/rtl/VADL.scala b/vadl/main/resources/templates/rtl/scala/VADL.scala similarity index 97% rename from vadl/main/resources/templates/rtl/VADL.scala rename to vadl/main/resources/templates/rtl/scala/VADL.scala index bf96ca291..683d151f8 100644 --- a/vadl/main/resources/templates/rtl/VADL.scala +++ b/vadl/main/resources/templates/rtl/scala/VADL.scala @@ -1,6 +1,7 @@ [# th:if="${package != ''}"]package [(${package})] [/]import chisel3._ +import chisel3.util._ object VADL { @@ -46,10 +47,14 @@ object VADL { } class MemReadPort[T <: Data](tpe: T, n: Int, addrWidth: Int) extends RegFileReadPort(Vec(n, tpe), addrWidth) { + val words = Input(Bits(log2Ceil(n+1).W)) + [# th:if="${memoryValid}"]val valid = Output(Bool())[/] } class MemWritePort[T <: Data](tpe: T, n: Int, addrWidth: Int) extends RegFileWritePort(Vec(n, tpe), addrWidth) { + val words = Input(Bits(log2Ceil(n+1).W)) + [# th:if="${memoryValid}"]val valid = Output(Bool())[/] } diff --git a/vadl/main/resources/templates/rtl/sv/SimMem.sv b/vadl/main/resources/templates/rtl/sv/SimMem.sv new file mode 100644 index 000000000..226b48578 --- /dev/null +++ b/vadl/main/resources/templates/rtl/sv/SimMem.sv @@ -0,0 +1,183 @@ +// Must match Data and Address in SimMem.cpp +`define SIMMEM_DATA_TYPE byte unsigned +`define SIMMEM_ADDR_TYPE longint unsigned + +`define SIMMEM_DATA_WIDTH $bits(`SIMMEM_DATA_TYPE) +`define SIMMEM_ADDR_WIDTH $bits(`SIMMEM_ADDR_TYPE) + +import "DPI-C" function chandle simmem_init( + input string name, + input string file +); + +import "DPI-C" function `SIMMEM_DATA_TYPE simmem_read( + input chandle ptr, + input `SIMMEM_ADDR_TYPE address +); + +import "DPI-C" function void simmem_write( + input chandle ptr, + input `SIMMEM_ADDR_TYPE address, + input `SIMMEM_DATA_TYPE data +); + +import "DPI-C" function `SIMMEM_ADDR_TYPE simmem_entry( + input chandle ptr +); + +import "DPI-C" function `SIMMEM_ADDR_TYPE simmem_symbol( + input chandle ptr, + input string symbol +); + +function chandle simmem_initial( + input string name +); + string file; + chandle ptr; + if ($value$plusargs($sformatf("%s=%%s", name), file)) begin + ptr = simmem_init(name, file); + if (ptr == null) begin + $fwrite(32'h8000_0002, "SimMem init failed.\n"); + $fatal; + end + end else begin + $fwrite(32'h8000_0002, "Init file for SimMem not provided. Use +%s=.\n", name); + $fatal; + end + return ptr; +endfunction + +function int randDelay(int max); + if (max > 0) begin + return $urandom % max; + end else begin + return 0; + end +endfunction + +module SimMemRead #( + parameter string NAME, + parameter int DATA_WIDTH, + parameter int ADDR_WIDTH, + parameter int WORD_WIDTH, + parameter int RANDOM_DELAY = 0 +) ( + input clock, + input reset, + + input enable, + input [ADDR_WIDTH-1:0] address, + input [$clog2(DATA_WIDTH/WORD_WIDTH+1)-1:0] words, + output reg [DATA_WIDTH-1:0] data, + output reg valid +); + + chandle ptr; + int delay = randDelay(RANDOM_DELAY); + + initial begin + ptr = simmem_initial(NAME); + end + + always @* + begin + data = 0; + valid = 0; + if (enable && delay == 0) begin + for (int i = 0; i < (words * WORD_WIDTH)/`SIMMEM_DATA_WIDTH; i++) begin + data[i*`SIMMEM_DATA_WIDTH +: `SIMMEM_DATA_WIDTH] = + `SIMMEM_DATA_WIDTH'(simmem_read(ptr, `SIMMEM_ADDR_WIDTH'(address + i))); + end + valid = 1; + end + end + + always @(posedge clock) begin + if (!reset) begin + if (enable) begin + if (delay == 0) begin + delay <= randDelay(RANDOM_DELAY); + end else begin + delay <= delay - 1; + end + end + end + end + +endmodule + +module SimMemWrite #( + parameter string NAME, + parameter int DATA_WIDTH, + parameter int ADDR_WIDTH, + parameter int WORD_WIDTH, + parameter int RANDOM_DELAY = 0 +) ( + input clock, + input reset, + + input enable, + input [ADDR_WIDTH-1:0] address, + input [DATA_WIDTH-1:0] data, + input [$clog2(DATA_WIDTH/WORD_WIDTH+1)-1:0] words, + output reg valid +); + + chandle ptr; + int delay = randDelay(RANDOM_DELAY); + + initial begin + ptr = simmem_initial(NAME); + end + + always @* + begin + valid = 0; + if (enable && delay == 0) begin + for (int i = 0; i < (words * WORD_WIDTH)/`SIMMEM_DATA_WIDTH; i++) begin + simmem_write(ptr, `SIMMEM_ADDR_WIDTH'(address + i), + `SIMMEM_DATA_WIDTH'(data[i*`SIMMEM_DATA_WIDTH +: `SIMMEM_DATA_WIDTH])); + end + valid = 1; + end + end + + always @(posedge clock) begin + if (!reset) begin + if (enable) begin + if (delay == 0) begin + delay <= randDelay(RANDOM_DELAY); + end else begin + delay <= delay - 1; + end + end + end + end + +endmodule + +module SimMemSymbols #( + parameter string NAME, + parameter int DATA_WIDTH, + parameter int ADDR_WIDTH, + parameter int WORD_WIDTH +) ( + input clock, + input reset, + + output reg [ADDR_WIDTH-1:0] entry, + output reg [ADDR_WIDTH-1:0] fromhost, + output reg [ADDR_WIDTH-1:0] tohost +); + + chandle ptr; + + initial begin + ptr = simmem_initial(NAME); + entry = ADDR_WIDTH'(simmem_entry(ptr)); + fromhost = ADDR_WIDTH'(simmem_symbol(ptr, "fromhost")); + tohost = ADDR_WIDTH'(simmem_symbol(ptr, "tohost")); + end + +endmodule \ No newline at end of file diff --git a/vadl/main/vadl/ast/BehaviorLowering.java b/vadl/main/vadl/ast/BehaviorLowering.java index 6e096fb67..526cac45c 100644 --- a/vadl/main/vadl/ast/BehaviorLowering.java +++ b/vadl/main/vadl/ast/BehaviorLowering.java @@ -1275,6 +1275,7 @@ private ExpressionNode visitSubCall(CallIndexExpr expr, ExpressionNode exprBefor } switch (viamArg) { + case Counter cnt -> call.add(cnt.registerTensor()); case Resource res -> call.add(res); case Logic logic -> call.add(logic); default -> throw new IllegalStateException(); diff --git a/vadl/main/vadl/ast/Definition.java b/vadl/main/vadl/ast/Definition.java index 4ce3fb058..189479c2c 100644 --- a/vadl/main/vadl/ast/Definition.java +++ b/vadl/main/vadl/ast/Definition.java @@ -4380,6 +4380,10 @@ public Identifier identifier() { return id; } + InstructionSetDefinition isaNode() { + return (InstructionSetDefinition) Objects.requireNonNull(isa.target()); + } + @Override R accept(DefinitionVisitor visitor) { return visitor.visit(this); diff --git a/vadl/main/vadl/ast/TypeChecker.java b/vadl/main/vadl/ast/TypeChecker.java index 279cea56b..d85bd2016 100644 --- a/vadl/main/vadl/ast/TypeChecker.java +++ b/vadl/main/vadl/ast/TypeChecker.java @@ -3400,7 +3400,7 @@ private void visitSubCall(CallIndexExpr expr, Type typeBeforeSubCall) { // Is the order important? Are the types of the arguments important? What rulse do even // aply. Are all the functions vararg? expr.type = fieldName.equals("unknown") ? Type.bool() : Type.void_(); - var argumentfreeFields = List.of("unknown", "compute", "verify"); + var argumentfreeFields = List.of("unknown", "compute", "verify"); // else: any num of args if (argumentfreeFields.contains(fieldName)) { if (!subCall.argsIndices.isEmpty()) { addErrorAndStopChecking( @@ -3410,14 +3410,6 @@ private void visitSubCall(CallIndexExpr expr, Type typeBeforeSubCall) { .build()); } return; - } else { - if (subCall.argsIndices.size() == 0) { - addErrorAndStopChecking( - error("Wrong Argument Number", - subCall.id) - .description("This subcall expects at least one argument.") - .build()); - } } } else if (expr.target instanceof Identifier id && id.target() instanceof StageDefinition stageDef) { diff --git a/vadl/main/vadl/ast/ViamLowering.java b/vadl/main/vadl/ast/ViamLowering.java index 3e019081c..2e4a1fcf8 100644 --- a/vadl/main/vadl/ast/ViamLowering.java +++ b/vadl/main/vadl/ast/ViamLowering.java @@ -213,7 +213,7 @@ public Specification generate(Ast ast) { // If there's only one most specialized ISA, return it if (mostSpecialized.size() == 1) { - return visitIsa(mergeIsa(mostSpecialized.getFirst())); + return visitAndMergeIsa(mostSpecialized.getFirst()); } if (mostSpecialized.size() > 1) { @@ -1382,12 +1382,20 @@ public Optional visit(InstructionDefinition definition) { return Optional.of(instruction); } - private InstructionSetArchitecture visitIsa(InstructionSetDefinition definition) { - var identifier = generateIdentifier(definition.identifier().name, definition.identifier()); + private InstructionSetArchitecture visitAndMergeIsa(InstructionSetDefinition definition) { + + var cached = definitionCache.get(definition); + if (cached != null && cached.isPresent()) { + return (InstructionSetArchitecture) cached.get(); + } + + var mergedDef = mergeIsa(definition); + + var identifier = generateIdentifier(mergedDef.identifier().name, mergedDef.identifier()); // FIXME: make this togroup instead of toList var allDefinitions = - definition.definitions.stream().map(this::fetch).flatMap(Optional::stream) + mergedDef.definitions.stream().map(this::fetch).flatMap(Optional::stream) .toList(); var formats = filterAndCastToInstance(allDefinitions, Format.class); var functions = filterAndCastToInstance(allDefinitions, Function.class); @@ -1411,7 +1419,7 @@ private InstructionSetArchitecture visitIsa(InstructionSetDefinition definition) registers.add(programCounter.registerTensor()); } - return new InstructionSetArchitecture( + var isa = new InstructionSetArchitecture( identifier, currentSpecification, formats, @@ -1425,6 +1433,9 @@ private InstructionSetArchitecture visitIsa(InstructionSetDefinition definition) memories, artificialResources ); + + definitionCache.put(definition, Optional.of(isa)); + return isa; } @Override @@ -1475,8 +1486,7 @@ public Optional visit(MemoryDefinition definition) { @Override public Optional visit(MicroArchitectureDefinition definition) { var identifier = generateIdentifier(definition.viamId, definition.identifier()); - var isa = visitIsa( - (InstructionSetDefinition) requireNonNull(definition.isa.target())); + var isa = visitAndMergeIsa(definition.isaNode()); var children = definition.definitions.stream().map(this::fetch).filter(Optional::isPresent) .map(Optional::orElseThrow).toList(); @@ -1512,7 +1522,7 @@ public Optional visit(ProcessorDefinition definition) { // for each isa in mip add definitions to definition list // create new isa ast node with list of definitions // visitIsa on created isa ast node - var isa = visitIsa(mergeIsa(definition.implementedIsaNode())); + var isa = visitAndMergeIsa(definition.implementedIsaNode()); var reset = (Procedure) definition.findCpuProcDef(CpuProcessDefinition.ProcessKind.RESET) .findFirst() diff --git a/vadl/main/vadl/configuration/RtlConfiguration.java b/vadl/main/vadl/configuration/RtlConfiguration.java index 60e2af374..f98699ed0 100644 --- a/vadl/main/vadl/configuration/RtlConfiguration.java +++ b/vadl/main/vadl/configuration/RtlConfiguration.java @@ -30,6 +30,8 @@ public class RtlConfiguration extends GeneralConfiguration { private String scalaTestPackageDir = "src/test/scala/"; + private String scalaTestResourcesDir = "src/test/resources/"; + /** * Top module name. This is initialized in {@link vadl.rtl.passes.RtlConfigurationPass}. */ @@ -42,13 +44,6 @@ public class RtlConfiguration extends GeneralConfiguration { @Nullable private String projectName = null; - /** - * Enum to select the dummy MiA to instantiate. - */ - public enum DummyMia { single, three, five } - - private DummyMia dummyMia = DummyMia.five; - /** * Enum to configure the external memory interface. * @@ -64,6 +59,10 @@ public enum Memory { decoupled, async } private boolean keepSignals = false; + private boolean emitDebugPrint = true; + + private boolean emitRVFI = false; + public RtlConfiguration(GeneralConfiguration generalConfig) { super(generalConfig); } @@ -86,6 +85,7 @@ public void setScalaPackageAndDirs(String scalaPackage) { this.scalaPackage = scalaPackage; this.scalaPackageDir = "src/main/scala/" + scalaPackage.replace('.', '/'); this.scalaTestPackageDir = "src/test/scala/" + scalaPackage.replace('.', '/'); + this.scalaTestResourcesDir = "src/test/resources"; } public String getScalaPackageDir() { @@ -96,6 +96,10 @@ public String getScalaTestPackageDir() { return scalaTestPackageDir; } + public String getScalaTestResourcesDir() { + return scalaTestResourcesDir; + } + public void setTopModule(@Nullable String topModule) { this.topModule = topModule; } @@ -134,14 +138,6 @@ public String getProjectName() { return Objects.requireNonNull(projectName); } - public DummyMia getDummyMia() { - return dummyMia; - } - - public void setDummyMia(DummyMia dummyMia) { - this.dummyMia = dummyMia; - } - public Memory getMemory() { return memory; } @@ -166,4 +162,21 @@ public boolean getKeepSignals() { public void setKeepSignals(boolean keepSignals) { this.keepSignals = keepSignals; } + + public boolean isEmitDebugPrint() { + return emitDebugPrint; + } + + public void setEmitDebugPrint(boolean emitDebugPrint) { + this.emitDebugPrint = emitDebugPrint; + } + + public boolean isEmitRVFI() { + return emitRVFI; + } + + public void setEmitRVFI(boolean emitRVFI) { + this.emitRVFI = emitRVFI; + } + } diff --git a/vadl/main/vadl/error/DiagnosticPrinter.java b/vadl/main/vadl/error/DiagnosticPrinter.java index 23a752b40..a9a97b90d 100644 --- a/vadl/main/vadl/error/DiagnosticPrinter.java +++ b/vadl/main/vadl/error/DiagnosticPrinter.java @@ -35,6 +35,7 @@ public class DiagnosticPrinter { public boolean forceRelativePaths = false; + public boolean forceUnixPaths = false; private final PrinterColors colors; private final Map> fileLineCache = new HashMap<>(); @@ -123,7 +124,7 @@ private void printMultiSourcePreview(Diagnostic diagnostic, StringBuilder builde builder.append(" %s╭──[%s]\n".formatted(colors.cyan(), diagnostic.multiLocation.primaryLocation().location().toIDEString( forceRelativePaths ? SourceLocation.IDEDetectionMode.RELATIVE : - SourceLocation.IDEDetectionMode.AUTO))); + SourceLocation.IDEDetectionMode.AUTO, forceUnixPaths))); builder.append(" │\n"); // TODO: Sort them accordig to their line number in the future @@ -162,7 +163,7 @@ private String sourceDelimiter(SourceLocation previous, var message = " %s⋮\n".formatted(colors.cyan()); message += " ╭─ %s\n".formatted( next.toIDEString(forceRelativePaths ? SourceLocation.IDEDetectionMode.RELATIVE : - SourceLocation.IDEDetectionMode.AUTO)); + SourceLocation.IDEDetectionMode.AUTO, forceUnixPaths)); return message; } else if (next.begin().line() == previous.end().line() + 1) { return " %s│\n".formatted(colors.cyan()); diff --git a/vadl/main/vadl/pass/PassOrders.java b/vadl/main/vadl/pass/PassOrders.java index 9167c111a..459eedad2 100644 --- a/vadl/main/vadl/pass/PassOrders.java +++ b/vadl/main/vadl/pass/PassOrders.java @@ -119,9 +119,13 @@ import vadl.rtl.passes.EmitBuildSbtPass; import vadl.rtl.passes.EmitCoreEmitPass; import vadl.rtl.passes.EmitCoreTestPass; +import vadl.rtl.passes.EmitElfSimPass; import vadl.rtl.passes.EmitModulesPass; +import vadl.rtl.passes.EmitRVFIOutputsPass; import vadl.rtl.passes.EmitRtlMakefilePass; import vadl.rtl.passes.EmitScalafmtConfigPass; +import vadl.rtl.passes.EmitSimMemCppPass; +import vadl.rtl.passes.EmitSimMemSvPass; import vadl.rtl.passes.EmitVadlLibPass; import vadl.rtl.passes.ForwardingLogicPass; import vadl.rtl.passes.HazardAnalysisPass; @@ -151,7 +155,6 @@ import vadl.viam.passes.algebraic_simplication.AlgebraicSimplificationPass; import vadl.viam.passes.behaviorRewrite.BehaviorRewritePass; import vadl.viam.passes.canonicalization.CanonicalizationPass; -import vadl.viam.passes.dummyPasses.DummyMiaPass; import vadl.viam.passes.functionInliner.ArtificialResInlinerPass; import vadl.viam.passes.functionInliner.FieldAccessInlinerPass; import vadl.viam.passes.functionInliner.FunctionInlinerPass; @@ -651,8 +654,6 @@ public static PassOrder rtl(RtlConfiguration config) throws IOException { order.add(new RtlConfigurationPass(config)); - // TODO: Remove once frontend creates it - order.add(new DummyMiaPass(config)); order.add(new StageOrderingPass(config)); order.add(new InstructionProgressGraphCreationPass(config)) @@ -662,16 +663,28 @@ public static PassOrder rtl(RtlConfiguration config) throws IOException { .add(new InstructionProgressGraphLowerPass(config)) .add(new InstructionProgressGraphNamePass(config)); - order.add(new HazardAnalysisPass(config)) - .add(new DebugOutputPass(config)); + addHtmlDump(order, config, + "mia-map", + "MiA after mapping instruction behavior"); + + order.add(new HazardAnalysisPass(config)); + + if (config.isEmitDebugPrint()) { + order.add(new DebugOutputPass(config)); + } + + order.add(new MiaMappingInlinePass(config)); + + if (config.isEmitRVFI()) { + order.add(new EmitRVFIOutputsPass(config)); + } - order.add(new MiaMappingInlinePass(config)) - .add(new ForwardingLogicPass(config)) + order.add(new ForwardingLogicPass(config)) .add(new ControlLogicPass(config)); addHtmlDump(order, config, - "mia", - "MiA after mapping and inlining instruction behavior"); + "mia-inline", + "MiA after inlining instruction behavior"); if (config.getDecoderOptions().getGenerator() != RTL_TABLE) { // Prepares and constructs the VDT, which is not used by the rtl-table strategy @@ -689,8 +702,13 @@ private static void addRtlEmitPasses(PassOrder order, RtlConfiguration config) { order.add(new EmitBuildSbtPass(config)) .add(new EmitModulesPass(config)) .add(new EmitVadlLibPass(config)) + // tests and simulation .add(new EmitCoreTestPass(config)) .add(new EmitCoreEmitPass(config)) + .add(new EmitElfSimPass(config)) + .add(new EmitSimMemSvPass(config)) + .add(new EmitSimMemCppPass(config)) + // build files .add(new EmitScalafmtConfigPass(config)) .add(new EmitRtlMakefilePass(config)) .add(new CleanupEmitDirectoryPass(config)); diff --git a/vadl/main/vadl/rtl/ipg/InstructionProgressGraph.java b/vadl/main/vadl/rtl/ipg/InstructionProgressGraph.java index d38830e0c..fdf109ae8 100644 --- a/vadl/main/vadl/rtl/ipg/InstructionProgressGraph.java +++ b/vadl/main/vadl/rtl/ipg/InstructionProgressGraph.java @@ -379,8 +379,8 @@ public static class NodeContext { */ NodeContext(Node node) { this.node = node; - this.instructions = new HashSet<>(); - this.nameHints = new HashSet<>(); + this.instructions = new LinkedHashSet<>(); + this.nameHints = new LinkedHashSet<>(); } /** diff --git a/vadl/main/vadl/rtl/ipg/nodes/RtlDebugPrintNode.java b/vadl/main/vadl/rtl/ipg/nodes/RtlDebugPrintNode.java index 31b3f69f1..bdb920753 100644 --- a/vadl/main/vadl/rtl/ipg/nodes/RtlDebugPrintNode.java +++ b/vadl/main/vadl/rtl/ipg/nodes/RtlDebugPrintNode.java @@ -34,7 +34,7 @@ /** * Node to print a debug message in RTL simulation. */ -public class RtlDebugPrintNode extends SideEffectNode { +public class RtlDebugPrintNode extends SideEffectNode implements RtlConditionalNode { private static final String PLACEHOLDER = "%[a-z]"; @@ -120,4 +120,9 @@ public Node shallowCopy() { public void accept(T visitor) { visitor.visit(this); } + + @Override + public Node asNode() { + return this; + } } diff --git a/vadl/main/vadl/rtl/ipg/nodes/RtlSelectByInstructionNode.java b/vadl/main/vadl/rtl/ipg/nodes/RtlSelectByInstructionNode.java index e8bb5ad4d..a3cc0b628 100644 --- a/vadl/main/vadl/rtl/ipg/nodes/RtlSelectByInstructionNode.java +++ b/vadl/main/vadl/rtl/ipg/nodes/RtlSelectByInstructionNode.java @@ -160,7 +160,7 @@ public void add(Instruction instruction, ExpressionNode value) { return; } } - var set = new HashSet(); + Set set = new LinkedHashSet<>(); set.add(instruction); instructions.add(set); values.add(value); diff --git a/vadl/main/vadl/rtl/map/MiaBuiltInCallMatcher.java b/vadl/main/vadl/rtl/map/MiaBuiltInCallMatcher.java index a6b268476..41a89e6ae 100644 --- a/vadl/main/vadl/rtl/map/MiaBuiltInCallMatcher.java +++ b/vadl/main/vadl/rtl/map/MiaBuiltInCallMatcher.java @@ -42,7 +42,7 @@ public class MiaBuiltInCallMatcher { interface Matcher { - boolean match(Node matchNode, MiaBuiltInCall mapNode, Set doneNodes); + boolean match(Node matchNode, BuiltInCall mapNode, Set doneNodes); } private static final IdentityHashMap MATCHERS = @@ -63,11 +63,13 @@ interface Matcher { }); MATCHERS.put(BuiltInTable.INSTRUCTION_READ, (matchNode, mapNode, doneNodes) -> { return matchNode instanceof ReadResourceNode n - && mapNode.matchResource(n.resourceDefinition()); + && mapNode instanceof MiaBuiltInCall call + && call.matchResource(n.resourceDefinition()); }); MATCHERS.put(BuiltInTable.INSTRUCTION_WRITE, (matchNode, mapNode, doneNodes) -> { return matchNode instanceof WriteResourceNode n - && mapNode.matchResource(n.resourceDefinition()); + && mapNode instanceof MiaBuiltInCall call + && call.matchResource(n.resourceDefinition()); }); MATCHERS.put(BuiltInTable.INSTRUCTION_COMPUTE, (matchNode, mapNode, doneNodes) -> { if (matchNode instanceof ConstantNode) { @@ -77,18 +79,24 @@ interface Matcher { }); MATCHERS.put(BuiltInTable.INSTRUCTION_ADDRESS, (matchNode, mapNode, doneNodes) -> { return matchNode.usages().anyMatch(use -> { - if (use instanceof ReadResourceNode n && mapNode.matchResource(n.resourceDefinition())) { - return (n.hasAddress() && n.address() == matchNode); - } - if (use instanceof WriteResourceNode n && mapNode.matchResource(n.resourceDefinition())) { - return (n.hasAddress() && n.address() == matchNode); + if (mapNode instanceof MiaBuiltInCall call) { + if (use instanceof ReadResourceNode n + && call.matchResource(n.resourceDefinition())) { + return (n.hasAddress() && n.address() == matchNode); + } + if (use instanceof WriteResourceNode n + && call.matchResource(n.resourceDefinition())) { + return (n.hasAddress() && n.address() == matchNode); + } } return false; }); }); MATCHERS.put(BuiltInTable.INSTRUCTION_RESULTS, (matchNode, mapNode, doneNodes) -> { return matchNode.usages().anyMatch(use -> { - if (use instanceof WriteResourceNode n && mapNode.matchResource(n.resourceDefinition())) { + if (use instanceof WriteResourceNode n + && mapNode instanceof MiaBuiltInCall call + && call.matchResource(n.resourceDefinition())) { return (n.hasAddress() && n.value() == matchNode && resolveDoneThroughUnaryNodes(matchNode, doneNodes)); } @@ -97,7 +105,8 @@ interface Matcher { }); MATCHERS.put(BuiltInTable.INSTRUCTION_READ_OR_FORWARD, (matchNode, mapNode, doneNodes) -> { return matchNode instanceof ReadResourceNode n - && mapNode.matchResource(n.resourceDefinition()); + && mapNode instanceof MiaBuiltInCall call + && call.matchResource(n.resourceDefinition()); }); MATCHERS.put(BuiltInTable.INSTRUCTION_VERIFY, (matchNode, mapNode, doneNodes) -> { return matchNode.usages().anyMatch(use -> @@ -158,17 +167,15 @@ private static boolean isCompute(Node matchNode) { * @return true, if node is part of compute */ private static boolean resolveCompute(Node matchNode, Set doneNodes) { - if (matchNode instanceof BuiltInCall) { - if (isCompute(matchNode)) { - return true; - } - return matchNode.inputs() - .allMatch(input -> doneNodes.contains(input) || resolveCompute(input, doneNodes)); - } if (matchNode instanceof ConstantNode) { return true; } - if (matchNode instanceof SelectNode || matchNode instanceof UnaryNode) { + if (matchNode instanceof BuiltInCall + || matchNode instanceof SelectNode + || matchNode instanceof UnaryNode) { + if (isCompute(matchNode)) { + return true; + } return matchNode.inputs() .allMatch(input -> doneNodes.contains(input) || resolveCompute(input, doneNodes)); } @@ -183,7 +190,7 @@ private static boolean resolveCompute(Node matchNode, Set doneNodes) { * @param nodes set of nodes to filter * @return filtered set of nodes */ - public Set match(MiaBuiltInCall mapNode, Set nodes, Set doneNodes) { + public Set match(BuiltInCall mapNode, Set nodes, Set doneNodes) { var matcher = MATCHERS.get(mapNode.builtIn()); if (matcher == null) { return new LinkedHashSet<>(); diff --git a/vadl/main/vadl/rtl/map/MiaMapping.java b/vadl/main/vadl/rtl/map/MiaMapping.java index 4829e2a1a..dfda647b1 100644 --- a/vadl/main/vadl/rtl/map/MiaMapping.java +++ b/vadl/main/vadl/rtl/map/MiaMapping.java @@ -32,6 +32,7 @@ import vadl.viam.ViamError; import vadl.viam.graph.Node; import vadl.viam.graph.ViamGraphError; +import vadl.viam.graph.dependency.BuiltInCall; import vadl.viam.graph.dependency.ExpressionNode; import vadl.viam.graph.dependency.MiaBuiltInCall; import vadl.viam.graph.dependency.SideEffectNode; @@ -332,7 +333,7 @@ public Set ipgNodes() { * @return true if decode context */ public boolean isDecode() { - return (node instanceof MiaBuiltInCall b && b.builtIn() == BuiltInTable.DECODE); + return (node instanceof BuiltInCall b && b.builtIn() == BuiltInTable.DECODE); } } diff --git a/vadl/main/vadl/rtl/passes/AbstractLogicPass.java b/vadl/main/vadl/rtl/passes/AbstractLogicPass.java index 15a9e060a..974bd1b9c 100644 --- a/vadl/main/vadl/rtl/passes/AbstractLogicPass.java +++ b/vadl/main/vadl/rtl/passes/AbstractLogicPass.java @@ -17,12 +17,14 @@ package vadl.rtl.passes; import java.io.IOException; +import java.util.Objects; import javax.annotation.Nullable; import vadl.configuration.RtlConfiguration; import vadl.pass.PassResults; import vadl.rtl.ipg.InstructionProgressGraph; import vadl.utils.Pair; import vadl.viam.InstructionSetArchitecture; +import vadl.viam.Logic; import vadl.viam.MicroArchitecture; import vadl.viam.Signal; import vadl.viam.Specification; @@ -30,6 +32,7 @@ import vadl.viam.graph.NodeList; import vadl.viam.graph.dependency.ConstantNode; import vadl.viam.graph.dependency.ExpressionNode; +import vadl.viam.graph.dependency.LetNode; import vadl.viam.graph.dependency.ReadRegTensorNode; import vadl.viam.graph.dependency.ReadSignalNode; import vadl.viam.graph.dependency.WriteResourceNode; @@ -47,11 +50,21 @@ public AbstractLogicPass(RtlConfiguration configuration) { super(configuration); } + protected Logic.Control getControl(MicroArchitecture mia) { + var control = (Logic.Control) mia.logic().stream() + .filter(Logic.Control.class::isInstance).findAny() + .orElseGet(() -> new Logic.Control(mia.identifier.append("control"))); + if (!mia.logic().contains(control)) { + mia.logic().add(control); + } + return control; + } + @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var isa = viam.isa().orElse(null); + var isa = viam.mia().map(MicroArchitecture::isa).orElse(null); var mia = viam.mia().orElse(null); if (isa == null || mia == null) { return null; @@ -60,6 +73,7 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce return execute(passResults, viam, isa, mia); } + @Nullable protected abstract Object execute(PassResults passResults, Specification viam, InstructionSetArchitecture isa, MicroArchitecture mia); @@ -83,6 +97,8 @@ protected ExpressionNode resolveStageValue(Stage stage, @Nullable ExpressionNode if (ipgNode == null) { return null; } + + // otherwise try to find registers or signals ExpressionNode stageNode = null; if (stage.prev() != null) { var outputInPrev = inline.stageRegisterMap().get(Pair.of(ipgNode, stage.prev())); @@ -118,6 +134,12 @@ protected ExpressionNode getStageSignalRead(Stage stage, ExpressionNode stageNod protected ExpressionNode getStageSignalRead(Stage stage, ExpressionNode stageNode, @Nullable MiaMappingInlinePass.Result inline) { + return getStageSignalRead(stage, stageNode, inline, null); + } + + protected ExpressionNode getStageSignalRead(Stage stage, ExpressionNode stageNode, + @Nullable MiaMappingInlinePass.Result inline, + @Nullable String fallbackName) { // just a constant if (stageNode instanceof ConstantNode c) { return c.copy(); @@ -134,15 +156,15 @@ protected ExpressionNode getStageSignalRead(Stage stage, ExpressionNode stageNod rd.staticCounterAccess()); } - // check if already placed into a signal + // check if already placed into a signal internal to the MiA WriteSignalNode wrSig = (WriteSignalNode) stageNode.usages() - .filter(WriteSignalNode.class::isInstance).findAny().orElse(null); - if (wrSig != null) { + .filter(WriteSignalNode.class::isInstance).findFirst().orElse(null); + if (wrSig != null && isMiaSignal(stage.mia(), wrSig.signal())) { return new ReadSignalNode(wrSig.signal()); // use existing signal } // create new signal - var name = "n_" + stageNode.id.numericId(); + var name = (fallbackName != null) ? fallbackName : "n_" + stageNode.id.numericId(); if (inline != null) { // name from ipg if possible var ipgNode = inline.inlineMap().inverse().get(stageNode); @@ -159,4 +181,17 @@ protected ExpressionNode getStageSignalRead(Stage stage, ExpressionNode stageNod return new ReadSignalNode(sig); } + private boolean isMiaSignal(MicroArchitecture mia, Signal signal) { + if (mia.signals().contains(signal)) { + return true; + } + if (mia.stages().stream().anyMatch(s -> s.signals().contains(signal))) { + return true; + } + if (mia.logic().stream().anyMatch(l -> l.signals().contains(signal))) { + return true; + } + return false; + } + } diff --git a/vadl/main/vadl/rtl/passes/ControlLogicPass.java b/vadl/main/vadl/rtl/passes/ControlLogicPass.java index 2059f686b..b09b7951f 100644 --- a/vadl/main/vadl/rtl/passes/ControlLogicPass.java +++ b/vadl/main/vadl/rtl/passes/ControlLogicPass.java @@ -17,6 +17,7 @@ package vadl.rtl.passes; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -34,6 +35,7 @@ import vadl.rtl.ipg.nodes.RtlConditionalMemNode; import vadl.rtl.ipg.nodes.RtlConditionalNode; import vadl.rtl.ipg.nodes.RtlConditionalReadNode; +import vadl.rtl.ipg.nodes.RtlResetSignalNode; import vadl.rtl.ipg.nodes.RtlValidSignalNode; import vadl.rtl.ipg.nodes.RtlWriteRegTensorNode; import vadl.rtl.utils.RtlSimplificationRules; @@ -87,9 +89,7 @@ protected Object execute(PassResults passResults, Specification viam, var inline = passResults.lastResultOf(MiaMappingInlinePass.class, MiaMappingInlinePass.Result.class); - var control = (Logic.Control) mia.logic().stream() - .filter(Logic.Control.class::isInstance).findAny() - .orElseGet(() -> new Logic.Control(mia.identifier.append("control"))); + var control = getControl(mia); // full registers @@ -107,12 +107,20 @@ protected Object execute(PassResults passResults, Specification viam, if (full == null) { // first stage if (stop != null) { - // empty stage with stop - fullRd = GraphUtils.not(new FuncCallNode(stop, new NodeList<>(), Type.bool())); + // empty stages with stop (provide this as signal) + var fullSig = new Signal(control.identifier.append(stage.simpleName() + "_full"), + Type.bool()); + control.addSignal(fullSig); + var notStop = GraphUtils.not(new FuncCallNode(stop, new NodeList<>(), Type.bool())); + control.behavior().addWithInputs(new WriteSignalNode(fullSig, notStop)); + + fullRd = new ReadSignalNode(fullSig); } else { // always full otherwise fullRd = Constant.Value.of(true).toNode(); } + // mask with reset + fullRd = GraphUtils.and(GraphUtils.not(new RtlResetSignalNode()), fullRd); } else { fullRd = new ReadRegTensorNode(full, new NodeList<>(), Type.bool(), null); } @@ -131,8 +139,9 @@ protected Object execute(PassResults passResults, Specification viam, rbCond = GraphUtils.or(rbCond, rb); } } else { - // the first stage only needs to roll back if it has data hazards (specifically on the pc) - var dhaz = dataHazard(isa, mia, control, stage, fullRdMap, inline, false); + // the first stage only needs to roll back if it has data hazards (especially on the pc) + // data hazards are evaluated here ignoring stages that will roll back anyway + var dhaz = dataHazard(isa, mia, control, stage, fullRdMap, inline, false, rollbackMap); rbCond = GraphUtils.and(rbCond, dhaz); rollbackMap.put(stage, rbCond); } @@ -142,16 +151,23 @@ protected Object execute(PassResults passResults, Specification viam, var stallMap = new HashMap(); for (int i = mia.stages().size() - 1; i >= 0; i--) { var stage = mia.stages().get(i); - var dhaz = dataHazard(isa, mia, control, stage, fullRdMap, inline, true); + var dhaz = dataHazard(isa, mia, control, stage, fullRdMap, inline, true, + Collections.emptyMap()); var fullRd = Objects.requireNonNull(fullRdMap.get(stage)); var extStall = extStall(stage, inline); + ExpressionNode stall; if (i == mia.stages().size() - 1) { - stallMap.put(stage, GraphUtils.and(GraphUtils.or(dhaz, extStall), fullRd)); + stall = GraphUtils.and(GraphUtils.or(dhaz, extStall), fullRd); } else { var stallNext = Objects.requireNonNull(stallMap.get(mia.stages().get(i + 1))); - stallMap.put(stage, GraphUtils.and( - GraphUtils.or(dhaz, extStall, stallNext), fullRd)); + stall = GraphUtils.and( + GraphUtils.or(dhaz, extStall, stallNext), fullRd); } + var sig = new Signal(control.identifier.append(stage.simpleName() + "_stall"), Type.bool()); + var wr = new WriteSignalNode(sig, stall); + control.behavior().addWithInputs(wr); + control.addSignal(sig); + stallMap.put(stage, new ReadSignalNode(sig)); } // enable signals @@ -185,18 +201,14 @@ protected Object execute(PassResults passResults, Specification viam, // patch side effects in stages for (Stage stage : mia.stages()) { var en = Objects.requireNonNull(control.getEnable(stage)); - var enRd = stage.behavior().add(new ReadSignalNode(en)); - var full = fullMap.get(stage); - ExpressionNode fullRd = Constant.Value.of(true).toNode(); - if (full != null) { - fullRd = new ReadRegTensorNode(full, new NodeList<>(), Type.bool(), null); - } - ExpressionNode finalFullRd = stage.behavior().add(fullRd); + var fullRd = Objects.requireNonNull(fullRdMap.get(stage)); stage.behavior().getNodes(RtlConditionalNode.class).forEach(condNode -> { - ExpressionNode enCond = enRd; + ExpressionNode enCond; if (condNode instanceof RtlConditionalMemNode || condNode instanceof RtlConditionalReadNode) { - enCond = finalFullRd; + enCond = stage.behavior().addWithInputs(fullRd.copy()); + } else { + enCond = stage.behavior().add(new ReadSignalNode(en)); } var cond = patchCondition(condNode.nullableCondition(), enCond); condNode.setCondition(cond); @@ -246,7 +258,9 @@ protected Object execute(PassResults passResults, Specification viam, // optimize Inliner.inlineFuncs(control.behavior()); - new RtlSimplifier(RtlSimplificationRules.rules).run(control.behavior()); + var simplifier = new RtlSimplifier(RtlSimplificationRules.rules); + simplifier.run(control.behavior()); + mia.stages().forEach(stage -> simplifier.run(stage.behavior())); // verify control.verify(); @@ -258,7 +272,8 @@ private ExpressionNode dataHazard(InstructionSetArchitecture isa, MicroArchitect Logic.Control control, Stage stage, Map fullRdMap, MiaMappingInlinePass.Result inline, - boolean excludePc) { + boolean excludePc, + Map rollbackMap) { var forwarding = (Logic.Forwarding) mia.logic().stream() .filter(Logic.Forwarding.class::isInstance).findAny().orElse(null); @@ -283,6 +298,10 @@ private ExpressionNode dataHazard(InstructionSetArchitecture isa, MicroArchitect var curStage = wr.effect(); while (curStage != null && !curStage.equals(stage)) { ExpressionNode and = Objects.requireNonNull(fullRdMap.get(curStage)); + var rb = rollbackMap.get(curStage); + if (rb != null) { + and = GraphUtils.and(and, GraphUtils.not(rb)); + } if (wr.node().nullableCondition() != null) { var enWr = resolveStageValue(curStage, wr.node().nullableCondition(), inline); if (enWr != null) { @@ -293,7 +312,7 @@ private ExpressionNode dataHazard(InstructionSetArchitecture isa, MicroArchitect and = GraphUtils.and(and, enRd); } if (forwarding != null) { - var enFwd = forwarding.getEnable(rd.asReadNode()); + var enFwd = forwarding.getEnableFrom(rd.asReadNode(), curStage); if (enFwd != null) { and = GraphUtils.and(and, GraphUtils.not(new ReadSignalNode(enFwd))); } @@ -312,7 +331,14 @@ private ExpressionNode dataHazard(InstructionSetArchitecture isa, MicroArchitect } } } - return control.behavior().addWithInputs(cond); + cond = control.behavior().addWithInputs(cond); + + var sigName = stage.simpleName() + "_dhaz" + (excludePc ? "" : "_pc"); + var sig = new Signal(control.identifier.append(sigName), Type.bool()); + var wr = new WriteSignalNode(sig, cond); + control.behavior().addWithInputs(wr); + control.addSignal(sig); + return new ReadSignalNode(sig); } @Nullable @@ -359,7 +385,7 @@ private ExpressionNode extStall(Stage stage, MiaMappingInlinePass.Result inline) .collect(Collectors.toSet()); var extStallCond = anyNotValid(extStallNodes); return extStallCond.map(stage.behavior()::addWithInputs) - .map(expr -> getStageSignalRead(stage, expr, inline)) + .map(expr -> getStageSignalRead(stage, expr, inline, stage.simpleName() + "_extstall")) .orElse(Constant.Value.of(false).toNode()); } diff --git a/vadl/main/vadl/rtl/passes/DebugOutputPass.java b/vadl/main/vadl/rtl/passes/DebugOutputPass.java index 1a2c77d39..6a6ad3d4d 100644 --- a/vadl/main/vadl/rtl/passes/DebugOutputPass.java +++ b/vadl/main/vadl/rtl/passes/DebugOutputPass.java @@ -17,6 +17,7 @@ package vadl.rtl.passes; import java.io.IOException; +import java.util.HashSet; import java.util.Objects; import javax.annotation.Nullable; import vadl.configuration.RtlConfiguration; @@ -24,8 +25,12 @@ import vadl.pass.PassResults; import vadl.rtl.ipg.nodes.RtlConditionalReadNode; import vadl.rtl.ipg.nodes.RtlDebugPrintNode; +import vadl.rtl.ipg.nodes.RtlReadMemNode; +import vadl.rtl.ipg.nodes.RtlWriteMemNode; import vadl.rtl.map.MiaMapping; import vadl.viam.Constant; +import vadl.viam.Logic; +import vadl.viam.MicroArchitecture; import vadl.viam.Resource; import vadl.viam.Specification; import vadl.viam.Stage; @@ -51,7 +56,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var isa = viam.isa().orElse(null); + var isa = viam.mia().map(MicroArchitecture::isa).orElse(null); var mia = viam.mia().orElse(null); if (isa == null || mia == null) { return null; @@ -63,6 +68,15 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce return null; } + // ignored resources + var ignore = new HashSet(); + if (passResults.hasRunPassOnce(EmitRVFIOutputsPass.class)) { + var rvfi = passResults.lastResultOf(EmitRVFIOutputsPass.class, Logic.RVFI.class); + ignore.addAll(rvfi.outputSignals()); + ignore.addAll(rvfi.registers()); + ignore.addAll(rvfi.signals()); + } + // add unknown instruction debug output // to first stage that writes any resource but the pc var readPc = Objects.requireNonNull(ipg.pcRead()); @@ -87,24 +101,29 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce // add read debug output for (RtlConditionalReadNode read : ipg.getNodes(RtlConditionalReadNode.class).toList()) { - if (read.asReadNode().resourceDefinition().equals(pc)) { + if (read.asReadNode().resourceDefinition().equals(pc) + || ignore.contains(read.asReadNode().resourceDefinition())) { continue; } var node = read.asReadNode(); var context = mapping.ensureContext(node); var addr = node.hasAddress() ? node.address() : null; var print = readWriteOutput(readPc, "rd", node.resourceDefinition(), - read.nullableCondition(), addr, node); + read.nullableCondition(), addr, node, + (node instanceof RtlReadMemNode rd) ? rd.words() : null); print = ipg.addWithInputs(print, ipg.getContext(node).instructions()); context.ipgNodes().add(print); } // add write debug output for (WriteResourceNode write : ipg.getNodes(WriteResourceNode.class).toList()) { + if (ignore.contains(write.resourceDefinition())) { + continue; + } var context = mapping.ensureContext(write); var addr = write.hasAddress() ? write.address() : null; var print = readWriteOutput(readPc, "wr", write.resourceDefinition(), write.condition(), - addr, write.value()); + addr, write.value(), (write instanceof RtlWriteMemNode wr) ? wr.words() : null); print = ipg.addWithInputs(print, ipg.getContext(write).instructions()); context.ipgNodes().add(print); } @@ -114,7 +133,9 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce private RtlDebugPrintNode readWriteOutput(ExpressionNode readPc, String op, Resource res, @Nullable ExpressionNode cond, - @Nullable ExpressionNode addr, ExpressionNode value) { + @Nullable ExpressionNode addr, + ExpressionNode value, + @Nullable ExpressionNode words) { var values = new NodeList<>(readPc); var sb = new StringBuilder().append("%x ").append(op).append(" ").append(res.simpleName()); if (addr != null) { @@ -128,6 +149,11 @@ private RtlDebugPrintNode readWriteOutput(ExpressionNode readPc, String op, Reso sb.append(" = %x"); values.add(value); + if (words != null) { + sb.append(" (%d)"); + values.add(words); + } + if (cond == null) { cond = Constant.Value.of(true).toNode(); } diff --git a/vadl/main/vadl/rtl/passes/EmitCoreEmitPass.java b/vadl/main/vadl/rtl/passes/EmitCoreEmitPass.java index 719526b9d..6a4787522 100644 --- a/vadl/main/vadl/rtl/passes/EmitCoreEmitPass.java +++ b/vadl/main/vadl/rtl/passes/EmitCoreEmitPass.java @@ -40,7 +40,7 @@ public PassName getName() { @Override protected String getTemplatePath() { - return "rtl/CoreEmit.scala"; + return "rtl/scala/CoreEmit.scala"; } @Override diff --git a/vadl/main/vadl/rtl/passes/EmitCoreTestPass.java b/vadl/main/vadl/rtl/passes/EmitCoreTestPass.java index 7e397b948..f47711206 100644 --- a/vadl/main/vadl/rtl/passes/EmitCoreTestPass.java +++ b/vadl/main/vadl/rtl/passes/EmitCoreTestPass.java @@ -42,7 +42,7 @@ public PassName getName() { @Override protected String getTemplatePath() { - return "rtl/CoreTest.scala"; + return "rtl/scala/CoreTest.scala"; } @Override diff --git a/vadl/main/vadl/rtl/passes/EmitElfSimPass.java b/vadl/main/vadl/rtl/passes/EmitElfSimPass.java new file mode 100644 index 000000000..2b154089f --- /dev/null +++ b/vadl/main/vadl/rtl/passes/EmitElfSimPass.java @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.rtl.passes; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import vadl.configuration.RtlConfiguration; +import vadl.pass.PassName; +import vadl.pass.PassResults; +import vadl.rtl.template.RtlTemplateRenderingPass; +import vadl.viam.Specification; + +/** + * Emit a chisel test that builds a simulator that can run ELF files, then runs the riscv-tests on + * it. + */ +public class EmitElfSimPass extends RtlTemplateRenderingPass { + + public EmitElfSimPass(RtlConfiguration configuration) { + super(configuration); + } + + @Override + public PassName getName() { + return PassName.of("Emit ElfSim.scala"); + } + + @Override + protected String getTemplatePath() { + return "rtl/scala/ElfSim.scala"; + } + + @Override + protected List createRenderInputs(PassResults passResults, + Specification specification, + Map baseVariables) { + var vars = new HashMap<>(baseVariables); + if (configuration().getResetVector() != null) { + vars.put("resetVector", configuration().getResetVector()); + } + return List.of( + new RenderInput(getSourceTestFilePath("ElfSim.scala"), vars) + ); + } +} diff --git a/vadl/main/vadl/rtl/passes/EmitModulesPass.java b/vadl/main/vadl/rtl/passes/EmitModulesPass.java index 5de7e90d4..e64162a4f 100644 --- a/vadl/main/vadl/rtl/passes/EmitModulesPass.java +++ b/vadl/main/vadl/rtl/passes/EmitModulesPass.java @@ -38,6 +38,7 @@ import vadl.viam.Constant; import vadl.viam.Counter; import vadl.viam.Logic; +import vadl.viam.MicroArchitecture; import vadl.viam.Resource; import vadl.viam.Signal; import vadl.viam.Specification; @@ -65,16 +66,15 @@ public PassName getName() { @Override protected String getTemplatePath() { - return "rtl/Module.scala"; + return "rtl/scala/Module.scala"; } @Override protected List createRenderInputs(PassResults passResults, Specification viam, Map base) { var mia = viam.mia().orElse(null); - var isa = viam.isa().orElse(null); - var mip = viam.processor().orElse(null); - if (mia == null || isa == null || mip == null) { + var isa = viam.mia().map(MicroArchitecture::isa).orElse(null); + if (mia == null || isa == null) { return List.of(); } @@ -94,8 +94,8 @@ protected List createRenderInputs(PassResults passResults, Specific vdt = null; } - var context = new HdlEmitContext(viam, isa, mia, mip, vdt, inlineRes.inlineMap(), resetVector, - configuration().getKeepSignals()); + var context = new HdlEmitContext(viam, isa, mia, viam.processor().orElse(null), vdt, + inlineRes.inlineMap(), resetVector, configuration().getKeepSignals()); List modules = new ArrayList<>(); mia.stages().stream().map(stage -> stage(context, stage)).forEach(modules::add); @@ -154,6 +154,9 @@ private void coreResetBehavior(Graph behavior, HdlEmitContext context, } // copy reset behavior, merge writes to get only one write per resource + if (context.processor() == null) { + return; + } var resetBehavior = context.processor().reset().behavior().copy(); GraphMergeUtils.mergeWritesOnBranches(resetBehavior); diff --git a/vadl/main/vadl/rtl/passes/EmitRVFIOutputsPass.java b/vadl/main/vadl/rtl/passes/EmitRVFIOutputsPass.java new file mode 100644 index 000000000..4ff5ff170 --- /dev/null +++ b/vadl/main/vadl/rtl/passes/EmitRVFIOutputsPass.java @@ -0,0 +1,252 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.rtl.passes; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nullable; +import vadl.configuration.RtlConfiguration; +import vadl.pass.PassName; +import vadl.pass.PassResults; +import vadl.rtl.ipg.InstructionProgressGraph; +import vadl.rtl.ipg.nodes.RtlConditionalReadNode; +import vadl.rtl.ipg.nodes.RtlReadMemNode; +import vadl.rtl.ipg.nodes.RtlReadRegTensorNode; +import vadl.rtl.ipg.nodes.RtlWriteMemNode; +import vadl.rtl.ipg.nodes.RtlWriteRegTensorNode; +import vadl.rtl.map.MiaMapping; +import vadl.rtl.utils.RtlSimplificationRules; +import vadl.rtl.utils.RtlSimplifier; +import vadl.utils.GraphUtils; +import vadl.viam.Constant; +import vadl.viam.InstructionSetArchitecture; +import vadl.viam.Logic; +import vadl.viam.MicroArchitecture; +import vadl.viam.RegisterTensor; +import vadl.viam.Resource; +import vadl.viam.Signal; +import vadl.viam.Specification; +import vadl.viam.Stage; +import vadl.viam.graph.Node; +import vadl.viam.graph.NodeList; +import vadl.viam.graph.dependency.ExpressionNode; +import vadl.viam.graph.dependency.ReadSignalNode; +import vadl.viam.graph.dependency.WriteResourceNode; +import vadl.viam.graph.dependency.WriteSignalNode; + +/** + * Adds outputs signals for the RISC-V Formal Interface. + */ +public class EmitRVFIOutputsPass extends AbstractLogicPass { + + public EmitRVFIOutputsPass(RtlConfiguration configuration) { + super(configuration); + } + + @Override + public PassName getName() { + return PassName.of("RTL RVFI Outputs"); + } + + @Nullable + @Override + protected Object execute(PassResults passResults, Specification viam, + InstructionSetArchitecture isa, MicroArchitecture mia) { + + var ipg = isa.expectExtension(InstructionProgressGraphExtension.class).ipg(); + var control = getControl(mia); + var inline = passResults.lastResultOf(MiaMappingInlinePass.class, + MiaMappingInlinePass.Result.class); + + var rvfi = new Logic.RVFI(mia.identifier.append("rvfi")); + mia.logic().add(rvfi); + rvfi.setMia(mia); + + // add outputs starting from stages that do not have a successors (the last stage) + mia.stages().stream() + .filter(this::isLastStage) + .forEach(stage -> addOutputs(new Context(ipg, inline, stage, control, rvfi))); + + // optimize and verify + new RtlSimplifier(RtlSimplificationRules.rules).run(rvfi.behavior()); + rvfi.behavior().verify(); + + return rvfi; + } + + private boolean isLastStage(Stage stage) { + var next = stage.next(); + return (next == null || next.isEmpty()); + } + + private record Context( + InstructionProgressGraph ipg, + MiaMappingInlinePass.Result inline, + Stage lastStage, + Logic.Control control, + Logic.RVFI rvfi + ) { + + ReadSignalNode getLastStageEnable() { + return getStageEnable(lastStage); + } + + ReadSignalNode getStageEnable(Stage stage) { + return new ReadSignalNode(control.getEnable(stage)); + } + + } + + private void addOutputs(Context context) { + addValid(context); + addOrderOutput(context); + + var fetch = Objects.requireNonNull(context.ipg().fetch()); + newSignalWrite("rvfi_insn", fetch, context); + + var unknown = Objects.requireNonNull(context.ipg().unknownInstruction()); + newSignalWrite("rvfi_trap", unknown, context); + + // read/write + var names = new HashSet(); + var reads = context.ipg().getNodes(RtlConditionalReadNode.class).toList(); + var writes = context.ipg().getNodes(WriteResourceNode.class).toList(); + reads.forEach(read -> + addReadOutput(read, names, context)); + writes.forEach(write -> + addWriteOutput(write, names, context)); + } + + private void addValid(Context context) { + var en = context.rvfi().behavior().add(context.getLastStageEnable()); + newSignalWrite("rvfi_valid", en, context); + } + + private void addOrderOutput(Context context) { + var orderReg = RegisterTensor.of( + context.rvfi().mia().identifier.append("rvfi_order_reg"), 64); + context.rvfi().addRegister(orderReg); + var orderRead = context.rvfi().behavior().addWithInputs(new RtlReadRegTensorNode( + orderReg, new NodeList<>(), orderReg.resultType(), + Constant.Value.of(true).toNode(), null + )); + var orderInc = GraphUtils.add( + orderRead, + Constant.Value.of(1, orderReg.resultType()).toNode() + ); + var orderWrite = new RtlWriteRegTensorNode( + orderReg, new NodeList<>(), orderInc, null, + context.getLastStageEnable() + ); + context.rvfi().behavior().addWithInputs(orderWrite); + newSignalWrite("rvfi_order", orderRead, context); + } + + private void newSignalWrite(String name, ExpressionNode value, Context context) { + // add output signal + var signal = new Signal( + context.rvfi().mia().identifier.append(name), + value.type().asDataType() + ); + + ExpressionNode out = null; + + if (value.ensureGraph().equals(context.rvfi().behavior())) { + out = value; + } else { + // value from stages + var curStage = context.lastStage(); + List stageList = new ArrayList<>(); + while (curStage != null) { + var stageValue = resolveStageValue(curStage, value, context.inline()); + if (stageValue == null) { + curStage = curStage.prev(); + if (curStage != null) { + stageList.add(curStage); + } + } else { + // build chain of registers enabled by stage enable signals + stageValue = context.rvfi().behavior().addWithInputs(stageValue); + for (Stage stage : stageList.reversed()) { + var reg = RegisterTensor.of( + context.rvfi().identifier.append(stage.simpleName() + "_" + name), + signal.resultType().bitWidth() + ); + context.rvfi().addRegister(reg); + context.rvfi().behavior().addWithInputs( + new RtlWriteRegTensorNode(reg, new NodeList<>(), stageValue, + null, context.getStageEnable(stage)) + ); + stageValue = context.rvfi().behavior().addWithInputs( + new RtlReadRegTensorNode(reg, new NodeList<>(), stageValue.type().asDataType(), + Constant.Value.of(true).toNode(), null) + ); + } + out = stageValue; + break; + } + } + } + + if (out != null) { + var write = context.rvfi().behavior().add(new WriteSignalNode(signal, out)); + var constTrue = context.rvfi().behavior().add(Constant.Value.of(true).toNode()); + write.setCondition(constTrue); + } + } + + private void addReadOutput(RtlConditionalReadNode read, Set names, Context context) { + var rdNode = read.asReadNode(); + String name = name(names, "rd", rdNode.resourceDefinition()); + if (rdNode.hasAddress()) { + newSignalWrite("rvfi_" + name + "_addr", rdNode.address(), context); + } + if (rdNode instanceof RtlReadMemNode rdMem) { + newSignalWrite("rvfi_" + name + "_words", rdMem.words(), context); + } + newSignalWrite("rvfi_" + name + "_rdata", rdNode, context); + newSignalWrite("rvfi_" + name + "_en", read.condition(), context); + } + + private void addWriteOutput(WriteResourceNode write, Set names, Context context) { + String name = name(names, "wr", write.resourceDefinition()); + if (write.hasAddress()) { + newSignalWrite("rvfi_" + name + "_addr", write.address(), context); + } + if (write instanceof RtlWriteMemNode wrMem) { + newSignalWrite("rvfi_" + name + "_words", wrMem.words(), context); + } + newSignalWrite("rvfi_" + name + "_wdata", write.value(), context); + newSignalWrite("rvfi_" + name + "_en", write.condition(), context); + } + + private String name(Set names, String prefix, Resource resource) { + int i = 0; + String name; + do { + name = prefix + resource.simpleName() + i; + i++; + } while (names.contains(name)); + names.add(name); + return name; + } +} diff --git a/vadl/main/vadl/rtl/passes/EmitSimMemCppPass.java b/vadl/main/vadl/rtl/passes/EmitSimMemCppPass.java new file mode 100644 index 000000000..d55ab95b6 --- /dev/null +++ b/vadl/main/vadl/rtl/passes/EmitSimMemCppPass.java @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.rtl.passes; + +import java.util.List; +import java.util.Map; +import vadl.configuration.RtlConfiguration; +import vadl.pass.PassName; +import vadl.pass.PassResults; +import vadl.rtl.template.RtlTemplateRenderingPass; +import vadl.viam.Specification; + +/** + * Emit C++ source for a simulation model of the memory. + */ +public class EmitSimMemCppPass extends RtlTemplateRenderingPass { + + public EmitSimMemCppPass(RtlConfiguration configuration) { + super(configuration); + } + + @Override + public PassName getName() { + return PassName.of("Emit SimMem.cpp"); + } + + @Override + protected String getTemplatePath() { + return "rtl/cpp/SimMem.cpp"; + } + + @Override + protected List createRenderInputs(PassResults passResults, + Specification specification, + Map baseVariables) { + return List.of( + new RenderInput(getResourceTestFilePath("SimMem.cpp"), baseVariables) + ); + } +} diff --git a/vadl/main/vadl/rtl/passes/EmitSimMemSvPass.java b/vadl/main/vadl/rtl/passes/EmitSimMemSvPass.java new file mode 100644 index 000000000..6c6ee63a4 --- /dev/null +++ b/vadl/main/vadl/rtl/passes/EmitSimMemSvPass.java @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.rtl.passes; + +import java.util.List; +import java.util.Map; +import vadl.configuration.RtlConfiguration; +import vadl.pass.PassName; +import vadl.pass.PassResults; +import vadl.rtl.template.RtlTemplateRenderingPass; +import vadl.viam.Specification; + +/** + * Emit System Verilog source for a simulation model of the memory. + */ +public class EmitSimMemSvPass extends RtlTemplateRenderingPass { + + public EmitSimMemSvPass(RtlConfiguration configuration) { + super(configuration); + } + + @Override + public PassName getName() { + return PassName.of("Emit SimMem.sv"); + } + + @Override + protected String getTemplatePath() { + return "rtl/sv/SimMem.sv"; + } + + @Override + protected List createRenderInputs(PassResults passResults, + Specification specification, + Map baseVariables) { + return List.of( + new RenderInput(getResourceTestFilePath("SimMem.sv"), baseVariables) + ); + } +} diff --git a/vadl/main/vadl/rtl/passes/EmitVadlLibPass.java b/vadl/main/vadl/rtl/passes/EmitVadlLibPass.java index 8c2f7e9ea..6c422be20 100644 --- a/vadl/main/vadl/rtl/passes/EmitVadlLibPass.java +++ b/vadl/main/vadl/rtl/passes/EmitVadlLibPass.java @@ -42,7 +42,7 @@ public PassName getName() { @Override protected String getTemplatePath() { - return "rtl/VADL.scala"; + return "rtl/scala/VADL.scala"; } @Override diff --git a/vadl/main/vadl/rtl/passes/ForwardingLogicPass.java b/vadl/main/vadl/rtl/passes/ForwardingLogicPass.java index 93762ff4e..9e0b14b77 100644 --- a/vadl/main/vadl/rtl/passes/ForwardingLogicPass.java +++ b/vadl/main/vadl/rtl/passes/ForwardingLogicPass.java @@ -65,9 +65,7 @@ protected Object execute(PassResults passResults, Specification viam, var inline = passResults.lastResultOf(MiaMappingInlinePass.class, MiaMappingInlinePass.Result.class); - var control = (Logic.Control) mia.logic().stream() - .filter(Logic.Control.class::isInstance).findAny() - .orElseGet(() -> new Logic.Control(mia.identifier.append("control"))); + var control = getControl(mia); var forwarding = (Logic.Forwarding) mia.logic().stream() .filter(Logic.Forwarding.class::isInstance).findAny() @@ -111,10 +109,21 @@ protected Object execute(PassResults passResults, Specification viam, } en = GraphUtils.and(en, new ReadSignalNode(control.getEnable(curStage))); - // forwarding value + // add forwarding enable for stage to fwd logic + var enFromSig = new Signal( + forwarding.identifier.append( + "fwd_" + res.simpleName() + i + "_" + curStage.simpleName() + "_en"), + Type.bool() + ); + forwarding.putEnableFrom(rd.asReadNode(), curStage, enFromSig); + var enFromWr = behavior.addWithInputs(new WriteSignalNode(enFromSig, en)); + en = enFromWr.value(); + + // forwarding enable and value var val = resolveStageValue(curStage, fwd.value(), inline); enList.add(en); valList.add(val); + curStage = curStage.prev(); } } @@ -171,7 +180,9 @@ protected Object execute(PassResults passResults, Specification viam, } // optimize - new RtlSimplifier(RtlSimplificationRules.rules).run(forwarding.behavior()); + var simplifier = new RtlSimplifier(RtlSimplificationRules.rules); + simplifier.run(forwarding.behavior()); + mia.stages().forEach(stage -> simplifier.run(stage.behavior())); // verify forwarding.verify(); diff --git a/vadl/main/vadl/rtl/passes/HazardAnalysisPass.java b/vadl/main/vadl/rtl/passes/HazardAnalysisPass.java index 04750d539..fd899f11f 100644 --- a/vadl/main/vadl/rtl/passes/HazardAnalysisPass.java +++ b/vadl/main/vadl/rtl/passes/HazardAnalysisPass.java @@ -34,6 +34,7 @@ import vadl.rtl.map.MiaMapping; import vadl.utils.Pair; import vadl.viam.Constant; +import vadl.viam.MicroArchitecture; import vadl.viam.Resource; import vadl.viam.Specification; import vadl.viam.Stage; @@ -41,6 +42,7 @@ import vadl.viam.graph.NodeList; import vadl.viam.graph.ViamGraphError; import vadl.viam.graph.dependency.ExpressionNode; +import vadl.viam.graph.dependency.LetNode; import vadl.viam.graph.dependency.ReadResourceNode; import vadl.viam.graph.dependency.SelectNode; import vadl.viam.graph.dependency.WriteResourceNode; @@ -62,7 +64,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var optIsa = viam.isa(); + var optIsa = viam.mia().map(MicroArchitecture::isa); if (optIsa.isEmpty()) { return null; } @@ -208,6 +210,9 @@ private ExpressionNode resolveForward( return res; } } + if (value instanceof LetNode let) { + return resolveForward(mapping, stageFrom, let.expression(), conditions); + } return null; } diff --git a/vadl/main/vadl/rtl/passes/InstructionProgressGraphCreationPass.java b/vadl/main/vadl/rtl/passes/InstructionProgressGraphCreationPass.java index 781ad1184..ee10ae154 100644 --- a/vadl/main/vadl/rtl/passes/InstructionProgressGraphCreationPass.java +++ b/vadl/main/vadl/rtl/passes/InstructionProgressGraphCreationPass.java @@ -47,6 +47,7 @@ import vadl.viam.Constant; import vadl.viam.Instruction; import vadl.viam.InstructionSetArchitecture; +import vadl.viam.MicroArchitecture; import vadl.viam.RegisterResource; import vadl.viam.RegisterTensor; import vadl.viam.Specification; @@ -94,7 +95,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var optIsa = viam.isa(); + var optIsa = viam.mia().map(MicroArchitecture::isa); if (optIsa.isEmpty()) { return null; } @@ -205,7 +206,6 @@ private void adaptReadWriteNodes(InstructionSetArchitecture isa, InstructionProg ipg.getNodes(WriteMemNode.class) .filter(write -> write.resourceDefinition().equals(mem)) .toList().forEach(write -> { - var words = new ConstantNode(Constant.Value.of(write.words(), wordType)); ExpressionNode value = write.value(); while (value instanceof TruncateNode truncate) { value = truncate.value(); @@ -213,6 +213,10 @@ private void adaptReadWriteNodes(InstructionSetArchitecture isa, InstructionProg if (value.type().asDataType().bitWidth() < valType.bitWidth()) { value = new ZeroExtendNode(value, valType); } + if (value.type().asDataType().bitWidth() > valType.bitWidth()) { + value = new TruncateNode(value, valType); + } + var words = new ConstantNode(Constant.Value.of(write.words(), wordType)); var rtlWrite = new RtlWriteMemNode(write.memory(), maxWrite.getAsInt(), words, write.address(), value, write.nullableCondition()); ipg.replaceAndDelete(write, rtlWrite); diff --git a/vadl/main/vadl/rtl/passes/InstructionProgressGraphLowerPass.java b/vadl/main/vadl/rtl/passes/InstructionProgressGraphLowerPass.java index d10c616c9..6f446f6ec 100644 --- a/vadl/main/vadl/rtl/passes/InstructionProgressGraphLowerPass.java +++ b/vadl/main/vadl/rtl/passes/InstructionProgressGraphLowerPass.java @@ -38,6 +38,7 @@ import vadl.rtl.utils.RtlSimplifier; import vadl.types.UIntType; import vadl.utils.GraphUtils; +import vadl.viam.MicroArchitecture; import vadl.viam.Specification; import vadl.viam.graph.Node; import vadl.viam.graph.dependency.ExpressionNode; @@ -62,7 +63,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var isa = viam.isa().orElse(null); + var isa = viam.mia().map(MicroArchitecture::isa).orElse(null); var mia = viam.mia().orElse(null); if (isa == null || mia == null) { return null; diff --git a/vadl/main/vadl/rtl/passes/InstructionProgressGraphMergePass.java b/vadl/main/vadl/rtl/passes/InstructionProgressGraphMergePass.java index 4c188cf29..fde500a28 100644 --- a/vadl/main/vadl/rtl/passes/InstructionProgressGraphMergePass.java +++ b/vadl/main/vadl/rtl/passes/InstructionProgressGraphMergePass.java @@ -33,6 +33,7 @@ import vadl.rtl.ipg.nodes.RtlReadMemNode; import vadl.rtl.ipg.nodes.RtlWriteMemNode; import vadl.rtl.map.MiaMapping; +import vadl.viam.MicroArchitecture; import vadl.viam.Specification; import vadl.viam.graph.Node; import vadl.viam.graph.dependency.ReadMemNode; @@ -60,7 +61,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var optIsa = viam.isa(); + var optIsa = viam.mia().map(MicroArchitecture::isa); if (optIsa.isEmpty()) { return null; } diff --git a/vadl/main/vadl/rtl/passes/InstructionProgressGraphNamePass.java b/vadl/main/vadl/rtl/passes/InstructionProgressGraphNamePass.java index 0ebed16fa..faa005834 100644 --- a/vadl/main/vadl/rtl/passes/InstructionProgressGraphNamePass.java +++ b/vadl/main/vadl/rtl/passes/InstructionProgressGraphNamePass.java @@ -43,6 +43,7 @@ import vadl.types.UIntType; import vadl.viam.Definition; import vadl.viam.Instruction; +import vadl.viam.MicroArchitecture; import vadl.viam.Resource; import vadl.viam.Specification; import vadl.viam.graph.Node; @@ -73,7 +74,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var optIsa = viam.isa(); + var optIsa = viam.mia().map(MicroArchitecture::isa); if (optIsa.isEmpty()) { return null; } @@ -240,6 +241,9 @@ private List nameInsWordSlice(RtlInstructionWordSliceNode node) { } private String nameInsSet(Set instructions, InstructionProgressGraph ipg) { + if (instructions.equals(ipg.instructions())) { + return "any"; + } if (instructions.size() > ipg.instructions().size() / 2) { // generate name for complement var complement = new LinkedHashSet<>(ipg.instructions()); @@ -261,7 +265,7 @@ private String nameInsSet(Set instructions, InstructionProgressGrap } // fallback to concatenation of all instruction names return instructions.stream() - .map(Definition::simpleName).collect(Collectors.joining("")); + .map(Definition::simpleName).sorted().collect(Collectors.joining("")); } private void nameSelect(InstructionProgressGraph ipg) { diff --git a/vadl/main/vadl/rtl/passes/MiaMappingCreationPass.java b/vadl/main/vadl/rtl/passes/MiaMappingCreationPass.java index 58bf904b4..3b30d44a5 100644 --- a/vadl/main/vadl/rtl/passes/MiaMappingCreationPass.java +++ b/vadl/main/vadl/rtl/passes/MiaMappingCreationPass.java @@ -19,12 +19,15 @@ import java.io.IOException; import java.util.ArrayDeque; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import vadl.configuration.GeneralConfiguration; import vadl.pass.Pass; @@ -35,15 +38,21 @@ import vadl.rtl.map.MiaMapping; import vadl.types.BuiltInTable; import vadl.types.MicroArchitectureType; +import vadl.utils.Pair; +import vadl.viam.MicroArchitecture; import vadl.viam.Specification; import vadl.viam.Stage; import vadl.viam.StageOutput; import vadl.viam.ViamError; import vadl.viam.graph.Node; +import vadl.viam.graph.ViamGraphError; +import vadl.viam.graph.control.AbstractEndNode; +import vadl.viam.graph.dependency.BuiltInCall; import vadl.viam.graph.dependency.ExpressionNode; -import vadl.viam.graph.dependency.MiaBuiltInCall; +import vadl.viam.graph.dependency.LetNode; import vadl.viam.graph.dependency.ReadStageOutputNode; import vadl.viam.graph.dependency.SideEffectNode; +import vadl.viam.graph.dependency.StageEffectNode; import vadl.viam.graph.dependency.WriteStageOutputNode; /** @@ -74,7 +83,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var optIsa = viam.isa(); + var optIsa = viam.mia().map(MicroArchitecture::isa); var optMia = viam.mia(); if (optIsa.isEmpty() || optMia.isEmpty()) { return null; @@ -90,6 +99,28 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce final var mapping = new MiaMapping(optMia.get(), ipg); for (Stage stage : stages) { + + // integrate stage effects into the data-flow + // the mia built-in calls attached to stage effects replace the output of their input node + stage.behavior().getNodes(AbstractEndNode.class).forEach(endNode -> { + endNode.sideEffects().forEach(sideEffect -> { + if (sideEffect instanceof StageEffectNode stageEffect) { + var call = stageEffect.miaCall(); + var inputs = call.inputs().toList(); + + ViamGraphError.ensure(inputs.size() == 1, stage.behavior(), call, + "MiA bult-in call can only have one input"); + + // replace usages, keep stage effect usages + var stageEffectUsages = inputs.getFirst().usages() + .filter(StageEffectNode.class::isInstance).toList(); + inputs.getFirst().replaceAtAllUsages(call); + stageEffectUsages.forEach(stageEffectUsage -> + stageEffectUsage.replaceInput(call, inputs.getFirst())); + } + }); + }); + // sources, maps, sinks based on input/output types of the nodes var sources = stage.behavior().getNodes().filter(this::isSource) .collect(Collectors.toCollection(LinkedHashSet::new)); @@ -118,22 +149,21 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce } } - // travers map nodes depth-first. only process nodes, if all inputs were processed earlier. - var matcher = new MiaBuiltInCallMatcher(); + // traverse map nodes depth-first. only process nodes, if all inputs were processed earlier. var q = new ArrayDeque(); sources.forEach(source -> { if (maps.contains(source)) { q.add(source); } else { - source.usages().filter(maps::contains).forEach(q::addLast); + resolveUsages(source).filter(maps::contains).forEach(q::addLast); } }); while (!q.isEmpty()) { var mapNode = q.removeFirst(); - var mapNodeInputs = mapNode.inputs().filter(this::instructionNode).toList(); + var mapNodeInputs = resolveInputs(mapNode).filter(this::instructionNode).toList(); // combine done nodes from inputs - var inputDoneSets = mapNode.inputs().map(done::get).filter(Objects::nonNull).toList(); + var inputDoneSets = resolveInputs(mapNode).map(done::get).filter(Objects::nonNull).toList(); if (inputDoneSets.size() < mapNodeInputs.size()) { // skip if not all inputs are processed yet, // only process node after the last input adds it to the queue @@ -142,43 +172,26 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce var inputDone = inputDoneSets.stream().flatMap(Collection::stream) .collect(Collectors.toSet()); - Set matched; - if (mapNode instanceof MiaBuiltInCall miaCall) { - // use matcher - matched = matcher.match(miaCall, ipgNodes, inputDone); - } else { - throw new PassError("Could not map node handling instructions %s in stage %s", - mapNode, stage); - } - - // grow matches up until nodes already done at inputs - var mapped = new LinkedHashSet(); - matched.forEach(match -> growInputs(match, mapped, inputDone)); + // map + var matchedMapped = mapNodes(mapNode, ipgNodes, inputDone, stage); // mark nodes in ipg - var inputContexts = mapNodeInputs.stream() - .map(input -> { - if (isMap(input)) { - return mapping.contexts().get(input); - } - if (input instanceof ReadStageOutputNode read) { - return writeContext.get(read.stageOutput()); - } - return null; - }) - .filter(Objects::nonNull).collect(Collectors.toList()); + var inputContexts = getInputContexts(mapNodeInputs, mapping, writeContext); var context = mapping.createContext(stage, mapNode, inputContexts); - var fixed = new LinkedHashSet<>(matched); - fixed.removeIf(inputDone::contains); - context.fixedIpgNodes().addAll(fixed); - context.ipgNodes().addAll(mapped); + context.fixedIpgNodes().addAll(matchedMapped.left()); + context.ipgNodes().addAll(matchedMapped.right()); + for (Node usage : mapNode.usages().toList()) { + if (usage instanceof StageEffectNode stageEffect) { + context.sideEffects().add(stageEffect); + } + } // save done set for this node - inputDone.addAll(mapped); + inputDone.addAll(matchedMapped.right()); done.put(mapNode, inputDone); // add map node usages to queue - mapNode.usages().filter(maps::contains).forEach(q::addLast); + resolveUsages(mapNode).filter(maps::contains).forEach(q::addLast); } // check if all mapped @@ -189,15 +202,20 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce // save write contexts to map at sinks for (Node sink : sinks) { if (sink instanceof WriteStageOutputNode write) { - var valueContext = mapping.contexts().get(write.value()); - if (valueContext == null) { + var inputs = resolveInputs(write).filter(n -> isMap(n) || isSource(n)).toList(); + if (inputs.isEmpty()) { + throw new PassError("Could not resolve input node at %s in stage %s", + write, stage); + } + var valueContexts = getInputContexts(inputs, mapping, writeContext); + if (valueContexts.isEmpty()) { throw new PassError("Could not load mapping context at %s in stage %s", write, stage); } - setSideEffect(valueContext, write); // set side effect reference to all inputs + setSideEffect(valueContexts.getFirst(), write); // set side effect reference to all inputs var output = write.stageOutput(); if (output != null) { - writeContext.put(write.stageOutput(), valueContext); + writeContext.put(write.stageOutput(), valueContexts.getFirst()); } } } @@ -213,6 +231,45 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce return mapping; } + @Nonnull + private List getInputContexts( + List mapNodeInputs, + MiaMapping mapping, + Map writeContext + ) { + return mapNodeInputs.stream() + .map(input -> { + if (isMap(input)) { + return mapping.contexts().get(input); + } + if (input instanceof ReadStageOutputNode read) { + return writeContext.get(read.stageOutput()); + } + return null; + }) + .filter(Objects::nonNull).collect(Collectors.toList()); + } + + private Pair, Set> mapNodes(Node mapNode, Set ipgNodes, Set inputDone, + Stage stage) { + var matcher = new MiaBuiltInCallMatcher(); + + Set matched; + if (mapNode instanceof BuiltInCall call) { + // use matcher + matched = matcher.match(call, ipgNodes, inputDone); + matched.removeAll(inputDone); + } else { + throw new PassError("Could not map node handling instructions %s in stage %s", + mapNode, stage); + } + + // grow matches up until nodes already done at inputs + var mapped = new LinkedHashSet(); + matched.forEach(match -> growInputs(match, mapped, inputDone)); + return Pair.of(matched, mapped); + } + // expression node with instruction output private boolean instructionNode(Node node) { if (node instanceof ExpressionNode expr) { @@ -230,12 +287,35 @@ private boolean isSource(Node node) { return false; } + // resolve usages stepping over let nodes + private Stream resolveUsages(Node node) { + return node.usages().flatMap(u -> { + if (u instanceof LetNode let) { + return resolveUsages(let); + } + return Stream.of(u); + }); + } + + // resolve inputs stepping over let nodes + private Stream resolveInputs(Node node) { + return node.inputs().flatMap(i -> { + if (i instanceof LetNode let) { + return resolveInputs(let); + } + return Stream.of(i); + }); + } + // expression node with instruction output _and_ instruction inputs private boolean isMap(Node node) { - if (node instanceof MiaBuiltInCall miaCall - && Set.of(BuiltInTable.DECODE, BuiltInTable.FETCH_NEXT).contains(miaCall.builtIn())) { + if (node instanceof BuiltInCall call + && Set.of(BuiltInTable.DECODE, BuiltInTable.FETCH_NEXT).contains(call.builtIn())) { return true; } + if (node instanceof LetNode) { + return false; + } if (node instanceof ExpressionNode expr) { return (expr.inputs().anyMatch(this::instructionNode) && instructionNode(expr)); } @@ -244,8 +324,7 @@ private boolean isMap(Node node) { // node with instruction inputs and _no_ instruction outputs private boolean isSink(Node node) { - return node.inputs().anyMatch(this::instructionNode) - && node.usages().noneMatch(this::instructionNode); + return node.inputs().anyMatch(this::instructionNode) && !instructionNode(node); } // recursively grow result nodes set up until (and excluding) the limit set of nodes @@ -258,7 +337,9 @@ private void growInputs(Node node, Set result, Set limit) { } private void setSideEffect(MiaMapping.NodeContext context, SideEffectNode node) { - context.sideEffects().add(node); + if (context.sideEffects().isEmpty()) { + context.sideEffects().add(node); + } // set side effects on all predecessor contexts still in the same stage context.pred().forEach(pred -> { if (context.stage().equals(pred.stage())) { diff --git a/vadl/main/vadl/rtl/passes/MiaMappingInlinePass.java b/vadl/main/vadl/rtl/passes/MiaMappingInlinePass.java index 5cc2f2c91..acc117fef 100644 --- a/vadl/main/vadl/rtl/passes/MiaMappingInlinePass.java +++ b/vadl/main/vadl/rtl/passes/MiaMappingInlinePass.java @@ -35,20 +35,21 @@ import vadl.rtl.ipg.nodes.RtlWriteRegTensorNode; import vadl.rtl.map.MiaMapping; import vadl.rtl.utils.SubgraphUtils; +import vadl.types.MicroArchitectureType; import vadl.utils.GraphUtils; import vadl.utils.Pair; +import vadl.viam.MicroArchitecture; import vadl.viam.RegisterTensor; import vadl.viam.Specification; import vadl.viam.Stage; import vadl.viam.graph.Node; import vadl.viam.graph.NodeList; import vadl.viam.graph.ViamGraphError; +import vadl.viam.graph.control.StartNode; import vadl.viam.graph.dependency.ConstantNode; import vadl.viam.graph.dependency.ExpressionNode; import vadl.viam.graph.dependency.ReadRegTensorNode; import vadl.viam.graph.dependency.SideEffectNode; -import vadl.viam.graph.dependency.WriteRegTensorNode; -import vadl.viam.graph.dependency.WriteStageOutputNode; /** * Inline nodes from the instruction progress graph into the MiA description based on the @@ -82,7 +83,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var optIsa = viam.isa(); + var optIsa = viam.mia().map(MicroArchitecture::isa); if (optIsa.isEmpty()) { return null; } @@ -107,7 +108,7 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce .collect(Collectors.toCollection(LinkedHashSet::new)); // copy subgraph to stage behavior - // add stage outputs to pass data between stages + // add stage output registers to pass data between stages var copyMap = SubgraphUtils.copy(stage.behavior(), stageNodes, (originalFrom, originalTo, copyFrom) -> { if (originalTo instanceof ExpressionNode originalExpr) { @@ -146,18 +147,11 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce } } - // delete stage outputs of mapping nodes - for (MiaMapping.NodeContext context : stageContexts) { - var node = context.node(); - for (Node u : node.usages().toList()) { - if (u instanceof WriteStageOutputNode wr) { - wr.safeDelete(true); - if (wr.stageOutput() != null) { - context.stage().removeOutput(wr.stageOutput()); - } - } - } - } + // delete control flow and mia stage outputs + // the mia stage outputs were replaced by stage registers + // this also removes all mapping nodes that were replaced by the inlined nodes + stage.behavior().getNodes(StartNode.class).forEach(Node::safeDelete); + stage.outputs().removeIf(out -> out.type() instanceof MicroArchitectureType); // verify stage stage.verify(); diff --git a/vadl/main/vadl/rtl/passes/MiaMappingOptimizePass.java b/vadl/main/vadl/rtl/passes/MiaMappingOptimizePass.java index ef6a720a2..54922ae11 100644 --- a/vadl/main/vadl/rtl/passes/MiaMappingOptimizePass.java +++ b/vadl/main/vadl/rtl/passes/MiaMappingOptimizePass.java @@ -39,6 +39,7 @@ import vadl.rtl.utils.GraphMergeUtils; import vadl.rtl.utils.RtlSimplificationRules; import vadl.rtl.utils.RtlSimplifier; +import vadl.viam.MicroArchitecture; import vadl.viam.Specification; import vadl.viam.Stage; import vadl.viam.graph.Node; @@ -67,7 +68,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var optIsa = viam.isa(); + var optIsa = viam.mia().map(MicroArchitecture::isa); if (optIsa.isEmpty()) { return null; } @@ -224,7 +225,7 @@ private void combineOutputs(List stages, MiaMapping mapping, // the bits we save passing between the stages outweigh the bits the node outputs // exception for truncate and extend nodes (no cost in hardware) private boolean isCandidate(Stage stage, MiaMapping mapping, Node ipgNode) { - if (ipgNode.inputs().noneMatch(node -> mapping.containsInStage(stage, node))) { + if (ipgNode.inputs().noneMatch(n -> notConst(n) && mapping.containsInStage(stage, n))) { if (ipgNode instanceof TruncateNode || ipgNode instanceof ZeroExtendNode || ipgNode instanceof SignExtendNode) { @@ -239,10 +240,14 @@ private boolean isCandidate(Stage stage, MiaMapping mapping, Node ipgNode) { // i.e., are saved when moving the node up private int sumInputsWithoutMoreUsages(Stage stage, MiaMapping mapping, Node ipgNode) { return ipgNode.inputs() - .filter(input -> !hasMoreUsages(stage, mapping, ipgNode, input)) + .filter(input -> notConst(input) && !hasMoreUsages(stage, mapping, ipgNode, input)) .mapToInt(this::bitWidth).sum(); } + private boolean notConst(Node node) { + return !(node instanceof ExpressionNode e) || !e.isConstant(); + } + private int bitWidth(Node input) { if (input instanceof ExpressionNode expr) { return expr.type().asDataType().bitWidth(); diff --git a/vadl/main/vadl/rtl/passes/RtlConfigurationPass.java b/vadl/main/vadl/rtl/passes/RtlConfigurationPass.java index fa1c51561..642087d1c 100644 --- a/vadl/main/vadl/rtl/passes/RtlConfigurationPass.java +++ b/vadl/main/vadl/rtl/passes/RtlConfigurationPass.java @@ -43,9 +43,10 @@ public PassName getName() { @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var processorName = viam.processor().map(Definition::simpleName).orElseThrow( - () -> Diagnostic.error("Processor definition required for emitting RTL", - viam.location()).build()); + var processorName = viam.processor().map(Definition::simpleName).orElseGet( + () -> viam.mia().map(Definition::simpleName).orElseThrow( + () -> Diagnostic.error("Processor or MiA definition required for emitting RTL", + viam.location()).build())); configuration().setTopModuleIfEmpty(processorName); configuration().setProjectNameIfEmpty(viam.simpleName()); diff --git a/vadl/main/vadl/rtl/template/HdlBehavior.java b/vadl/main/vadl/rtl/template/HdlBehavior.java index 0ae5d07fe..f1305e9ca 100644 --- a/vadl/main/vadl/rtl/template/HdlBehavior.java +++ b/vadl/main/vadl/rtl/template/HdlBehavior.java @@ -251,6 +251,11 @@ String handle(ConstantNode node) { .addContext(node); } + @Handler + String handle(LetNode node) { + return dispatch(node.expression()); + } + @Handler String handle(SliceNode node) { var slices = node.bitSlice().parts() @@ -291,7 +296,7 @@ String handle(RtlDecodeTreeNode decodeTreeNode) { // The name is prefixed with 'dec_' by now to indicate that the signal will be assigned // by the decode tree. var name = module.context().name(usage, module.localNames(), fallbackName(usage)); - var id = def.identifier.append("dec_" + name); + var id = def.identifier.append(name); var signal = new Signal(id, ((ExpressionNode) usage).type().asDataType()); signals.add(signal); @@ -404,6 +409,11 @@ String handle(RtlSelectByInstructionNode node) { .collect(Collectors.joining(", ")) + "))"; } + @Handler + String handle(RtlResetSignalNode node) { + return "reset.asBool"; + } + @Handler String handle(RtlValidSignalNode node) { if (node.validNode() instanceof RtlReadMemNode read) { @@ -448,6 +458,17 @@ String handle(ReadResourceNode node) { addrEnd, false, null )); + if (node instanceof RtlReadMemNode read) { + var wordsEnd = new HdlConnection.ExpressionEndpoint( + read.words(), + dispatch(read.words()) + ); + module.connections().add(new HdlConnection( + new HdlConnection.ExpressionEndpoint(node, expr + ".words"), + wordsEnd, + false, null + )); + } } if (res.isPresent() || node instanceof ReadSignalNode) { return expr; @@ -513,6 +534,17 @@ void handle(WriteResourceNode node) { false, null )); } + if (node instanceof RtlWriteMemNode write) { + var wordsEnd = new HdlConnection.ExpressionEndpoint( + write.words(), + dispatch(write.words()) + ); + module.connections().add(new HdlConnection( + new HdlConnection.ExpressionEndpoint(node, expr + ".words"), + wordsEnd, + false, null + )); + } var valueEnd = new HdlConnection.ExpressionEndpoint( node.value(), dispatch(node.value()) + ".asTypeOf(" + expr + ".data)" @@ -557,11 +589,12 @@ public static boolean isSignal(ExpressionNode node) { || node instanceof UnaryNode || node instanceof RtlValidSignalNode || node instanceof ReadResourceNode - || node instanceof RtlDecodeTreeNode) { + || node instanceof RtlDecodeTreeNode + || node instanceof RtlResetSignalNode + || (node instanceof BuiltInCall builtIn && builtIn.arguments().size() < 2)) { return false; } return (node.usageCount() > 1 - || node instanceof LetNode || node instanceof SelectNode || node instanceof RtlIsInstructionNode || node instanceof RtlInvalidInstructionNode); diff --git a/vadl/main/vadl/rtl/template/HdlConnection.java b/vadl/main/vadl/rtl/template/HdlConnection.java index d511a3058..36c89a481 100644 --- a/vadl/main/vadl/rtl/template/HdlConnection.java +++ b/vadl/main/vadl/rtl/template/HdlConnection.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; import vadl.viam.Resource; +import vadl.viam.Signal; import vadl.viam.graph.Node; /** @@ -64,6 +65,37 @@ public String rtlName() { } } + /** + * Create a connection between two port endpoints. + * + * @param end1 port endpoint + * @param end2 port endpoint + * @return connection, not bidirectional for signals + */ + public static HdlConnection of(PortEndpoint end1, PortEndpoint end2) { + if (end1.port().resource() instanceof Signal && end2.port().resource() instanceof Signal) { + var child1 = end1.child() != null && end2.child() == null; + var child2 = end1.child() == null && end2.child() != null; + var childBoth = end1.child() != null && end2.child() != null; + var swap = false; + if (child1 && end1.port().output() && end2.port().output()) { + swap = true; + } + if (child2 && end1.port().input() && end2.port().input()) { + swap = true; + } + if (childBoth && end1.port().output() && end2.port().input()) { + swap = true; + } + if (swap) { + return new HdlConnection(end2, end1, false, null); + } else { + return new HdlConnection(end1, end2, false, null); + } + } + return new HdlConnection(end1, end2, true, null); + } + /** * References a resource of the containing module. This is either a resource from the VIAM or a * resource created during HDL generation to hold the value of a node from the module's behavior. diff --git a/vadl/main/vadl/rtl/template/HdlEmitContext.java b/vadl/main/vadl/rtl/template/HdlEmitContext.java index 89bf26e58..8b7736a86 100644 --- a/vadl/main/vadl/rtl/template/HdlEmitContext.java +++ b/vadl/main/vadl/rtl/template/HdlEmitContext.java @@ -48,7 +48,7 @@ public record HdlEmitContext( Specification viam, InstructionSetArchitecture isa, MicroArchitecture mia, - Processor processor, + @Nullable Processor processor, @Nullable vadl.vdt.model.Node vdt, BiMap inlineMap, @Nullable Signal resetVector, diff --git a/vadl/main/vadl/rtl/template/HdlModule.java b/vadl/main/vadl/rtl/template/HdlModule.java index a2a7a34ad..9d69d427b 100644 --- a/vadl/main/vadl/rtl/template/HdlModule.java +++ b/vadl/main/vadl/rtl/template/HdlModule.java @@ -164,7 +164,7 @@ public Set localNames() { public Map createVariables() { return Map.of( "name", name, - "syncReset", definition instanceof MicroArchitecture, + "syncReset", false, //definition instanceof MicroArchitecture, "children", children.stream().map(this::childVars).toList(), "resources", resources.stream().map(this::resourceVars).toList(), "ports", ports.stream().map(this::portVars).toList(), diff --git a/vadl/main/vadl/rtl/template/HdlWiring.java b/vadl/main/vadl/rtl/template/HdlWiring.java index e233fe5af..befd1c7c2 100644 --- a/vadl/main/vadl/rtl/template/HdlWiring.java +++ b/vadl/main/vadl/rtl/template/HdlWiring.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; import vadl.viam.Resource; +import vadl.viam.Signal; /** * Wires HDL module ports after they are created by {@link HdlBehavior} from module behavior. @@ -91,11 +92,8 @@ private static boolean connect(HdlModule module, HdlConnection.PortEndpoint end) var otherPort = new HdlPort(name, end.port().resource(), end.port().read(), !end.port().output(), end.port().nodes()); otherPort = addOrMergePort(otherChild.get(), otherPort); - module.addConnection(new HdlConnection( - end, - new HdlConnection.PortEndpoint(otherChild.get(), otherPort), - true, null - )); + module.addConnection( + HdlConnection.of(end, new HdlConnection.PortEndpoint(otherChild.get(), otherPort))); } else { // add port up in the hierarchy var name = module.context().name(end.port().nodes(), module.portNames(), @@ -103,11 +101,8 @@ private static boolean connect(HdlModule module, HdlConnection.PortEndpoint end) var upPort = new HdlPort(name, end.port().resource(), end.port().read(), end.port().output(), end.port().nodes()); upPort = addOrMergePort(module, upPort); - module.addConnection(new HdlConnection( - end, - new HdlConnection.PortEndpoint(null, upPort), - true, null - )); + module.addConnection( + HdlConnection.of(end, new HdlConnection.PortEndpoint(null, upPort))); } } diff --git a/vadl/main/vadl/rtl/template/RtlTemplateRenderingPass.java b/vadl/main/vadl/rtl/template/RtlTemplateRenderingPass.java index b23b69d09..2b9b099c6 100644 --- a/vadl/main/vadl/rtl/template/RtlTemplateRenderingPass.java +++ b/vadl/main/vadl/rtl/template/RtlTemplateRenderingPass.java @@ -24,6 +24,7 @@ import vadl.pass.PassResults; import vadl.template.AbstractMultiTemplateRenderingPass; import vadl.viam.Definition; +import vadl.viam.MicroArchitecture; import vadl.viam.Specification; import vadl.viam.ViamError; @@ -70,8 +71,9 @@ protected Map getBaseVariables(PassResults passResults, Specific vars.put("package", configuration.getScalaPackage()); vars.put("topModule", configuration.getTopModule()); vars.put("projectName", configuration.getProjectName()); - vars.put("isaName", viam.isa().map(Definition::simpleName).orElseThrow(() -> - Diagnostic.error("Can not emit RTL without ISA", viam.location()).build())); + vars.put("isaName", viam.mia().map(MicroArchitecture::isa) + .map(Definition::simpleName).orElseThrow(() -> + Diagnostic.error("Can not emit RTL without ISA", viam.location()).build())); return vars; } @@ -83,6 +85,10 @@ protected String getSourceTestFilePath(String filename) { return configuration.getScalaTestPackageDir() + "/" + filename; } + protected String getResourceTestFilePath(String filename) { + return configuration.getScalaTestResourcesDir() + "/" + filename; + } + protected Map mergeVariables(Map baseVariables, Map variables) { var result = new HashMap<>(baseVariables); diff --git a/vadl/main/vadl/rtl/utils/RtlSimplificationRules.java b/vadl/main/vadl/rtl/utils/RtlSimplificationRules.java index 2f4030aae..06bcf8303 100644 --- a/vadl/main/vadl/rtl/utils/RtlSimplificationRules.java +++ b/vadl/main/vadl/rtl/utils/RtlSimplificationRules.java @@ -18,15 +18,17 @@ import java.math.BigInteger; import java.util.ArrayList; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.stream.Stream; import vadl.rtl.ipg.nodes.RtlSelectByInstructionNode; import vadl.types.BuiltInTable; import vadl.utils.BigIntUtils; +import vadl.utils.GraphUtils; import vadl.viam.Constant; import vadl.viam.graph.Node; +import vadl.viam.graph.dependency.BuiltInCall; import vadl.viam.graph.dependency.ConstantNode; import vadl.viam.graph.dependency.ExpressionNode; import vadl.viam.graph.dependency.SelectNode; @@ -68,6 +70,9 @@ public class RtlSimplificationRules { rules.add(new AndWithOnesSimplificationRule()); rules.add(new OrWithOnesSimplificationRule()); rules.add(new OrWithZerosSimplificationRule()); + rules.add(new NotAndSimplificationRule()); + rules.add(new NotOrSimplificationRule()); + rules.add(new NotNotSimplificationRule()); rules.add(new SelectWithEqCasesSimplificationRule()); rules.add(new SelectWithConstCondSimplificationRule()); rules.add(new SelByInstrEqCasesSimplificationRule()); @@ -156,6 +161,129 @@ public Optional simplify(Node node) { } } + /** + * Eliminate double logical negations. + */ + public static class NotNotSimplificationRule implements AlgebraicSimplificationRule { + @Override + public Optional simplify(Node node) { + if (node instanceof ExpressionNode n) { + var matcher = + new BuiltInMatcher(BuiltInTable.NOT, + new BuiltInMatcher(BuiltInTable.NOT, List.of())); + + var matchings = TreeMatcher.matches(Stream.of(node), matcher); + if (!matchings.isEmpty() && n instanceof BuiltInCall not1 + && not1.arg(0) instanceof BuiltInCall not2) { + return Optional.of(not2.arg(0)); + } + } + return Optional.empty(); + } + } + + /** + * Push in logical negations inside and built-in calls. + */ + public static class NotAndSimplificationRule implements AlgebraicSimplificationRule { + @Override + public Optional simplify(Node node) { + if (node instanceof ExpressionNode n) { + var matcher = + new BuiltInMatcher(BuiltInTable.NOT, + new BuiltInMatcher(BuiltInTable.AND, List.of())); + + var matchings = TreeMatcher.matches(Stream.of(node), matcher); + if (!matchings.isEmpty() && n instanceof BuiltInCall not + && not.arg(0) instanceof BuiltInCall and) { + if (and.usageCount() > 1) { + return Optional.empty(); + } + return and.arguments().stream() + .map(GraphUtils::not).reduce(GraphUtils::or) + .map(Node.class::cast); + } + } + return Optional.empty(); + } + } + + /** + * Push in logical negations inside or built-in calls. + */ + public static class NotOrSimplificationRule implements AlgebraicSimplificationRule { + @Override + public Optional simplify(Node node) { + if (node instanceof ExpressionNode n) { + var matcher = + new BuiltInMatcher(BuiltInTable.NOT, + new BuiltInMatcher(BuiltInTable.OR, List.of())); + + var matchings = TreeMatcher.matches(Stream.of(node), matcher); + if (!matchings.isEmpty() && n instanceof BuiltInCall not + && not.arg(0) instanceof BuiltInCall or) { + if (or.usageCount() > 1) { + return Optional.empty(); + } + return or.arguments().stream() + .map(GraphUtils::not).reduce(GraphUtils::and) + .map(Node.class::cast); + } + } + return Optional.empty(); + } + } + + /** + * Move nested and built-in calls to the right. + */ + public static class NestedAndSimplificationRule implements AlgebraicSimplificationRule { + @Override + public Optional simplify(Node node) { + if (node instanceof ExpressionNode n) { + var matcher = + new BuiltInMatcher(BuiltInTable.AND, + new BuiltInMatcher(BuiltInTable.AND, + new BuiltInMatcher(BuiltInTable.AND, List.of()).not()), + new AnyNodeMatcher()); + + var matchings = TreeMatcher.matches(Stream.of(node), matcher); + if (!matchings.isEmpty() && n instanceof BuiltInCall and1 + && and1.arg(0) instanceof BuiltInCall and2) { + return Optional.of(GraphUtils.and( + and2.arg(0), GraphUtils.and(and2.arg(1), and1.arg(1)) + )); + } + } + return Optional.empty(); + } + } + + /** + * Move nested or built-in calls to the right. + */ + public static class NestedOrSimplificationRule implements AlgebraicSimplificationRule { + @Override + public Optional simplify(Node node) { + if (node instanceof ExpressionNode n) { + var matcher = + new BuiltInMatcher(BuiltInTable.OR, + new BuiltInMatcher(BuiltInTable.OR, + new BuiltInMatcher(BuiltInTable.OR, List.of()).not()), + new AnyNodeMatcher()); + + var matchings = TreeMatcher.matches(Stream.of(node), matcher); + if (!matchings.isEmpty() && n instanceof BuiltInCall or1 + && or1.arg(0) instanceof BuiltInCall or2) { + return Optional.of(GraphUtils.or( + or2.arg(0), GraphUtils.or(or2.arg(1), or1.arg(1)) + )); + } + } + return Optional.empty(); + } + } + /** * Simplify select with equal cases. */ @@ -199,7 +327,7 @@ public Optional simplify(Node node) { return Optional.of(first); } // optimize values (merges equal values) - var values = new HashSet<>(n.values()); + var values = new LinkedHashSet<>(n.values()); if (values.size() < n.values().size()) { for (ExpressionNode value : values) { if (n.values().stream().filter(value::equals).count() > 1) { diff --git a/vadl/main/vadl/rtl/utils/SubgraphUtils.java b/vadl/main/vadl/rtl/utils/SubgraphUtils.java index 7ea1ce70c..336c30444 100644 --- a/vadl/main/vadl/rtl/utils/SubgraphUtils.java +++ b/vadl/main/vadl/rtl/utils/SubgraphUtils.java @@ -24,6 +24,7 @@ import javax.annotation.Nullable; import vadl.viam.graph.Graph; import vadl.viam.graph.Node; +import vadl.viam.graph.ViamGraphError; /** * Utils for copying subgraphs. @@ -90,22 +91,35 @@ private static Node addWithMissingInputs(Graph dest, Node original, Node copy, S return cached; } - Streams.forEachPair(original.inputs(), copy.inputs(), (originalInput, copyInput) -> { + var originalInputs = original.inputs().toList(); + var copyInputs = copy.inputs().toList(); - var newInput = copyInput; + if (originalInputs.size() != copyInputs.size()) { + throw new ViamGraphError( + "Input mismatch: %d original inputs, %d copy inputs", + originalInputs.size(), copyInputs.size()) + .addContext("original", original) + .addContext("copy", copy); + } - if (set.contains(originalInput)) { - newInput = addWithMissingInputs(dest, originalInput, copyInput, set, missingInput, cache); - } else { - var suppliedInput = missingInput.supply(original, originalInput, copy); - if (suppliedInput != null) { - newInput = dest.addWithInputs(suppliedInput); - } - } + Streams.forEachPair(originalInputs.stream(), copyInputs.stream(), + (originalInput, copyInput) -> { - copy.replaceInput(copyInput, newInput); + var newInput = copyInput; - }); + if (set.contains(originalInput)) { + newInput = + addWithMissingInputs(dest, originalInput, copyInput, set, missingInput, cache); + } else { + var suppliedInput = missingInput.supply(original, originalInput, copy); + if (suppliedInput != null) { + newInput = dest.addWithInputs(suppliedInput); + } + } + + copy.replaceInput(copyInput, newInput); + + }); var result = dest.add(copy); diff --git a/vadl/main/vadl/utils/SourceLocation.java b/vadl/main/vadl/utils/SourceLocation.java index 5c94066f2..3806c5597 100644 --- a/vadl/main/vadl/utils/SourceLocation.java +++ b/vadl/main/vadl/utils/SourceLocation.java @@ -21,6 +21,7 @@ import com.google.common.collect.Streams; import java.io.IOException; import java.net.URI; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; @@ -222,6 +223,17 @@ public String toIDEString() { *

*/ public String toIDEString(IDEDetectionMode mode) { + return toIDEString(mode, false); + } + + /** + * Produces version that is easily understandable for IDE's. + * + *

E.g.: {@code SourceLocation("relative/path/to/file.vadl", (1, 3), (2, 4))} + * becomes {@code "relative/path/to/file.vadl:1:3"} + *

+ */ + public String toIDEString(IDEDetectionMode mode, boolean forceUnixPaths) { if (!this.isValid()) { return "Source Location was lost"; } @@ -237,6 +249,9 @@ public String toIDEString(IDEDetectionMode mode) { var currentWorkingDir = Paths.get(System.getProperty("user.dir")); printablePath = currentWorkingDir.relativize(filePath).toFile().getPath(); } + if (forceUnixPaths) { + printablePath = printablePath.replace(FileSystems.getDefault().getSeparator(), "/"); + } return printablePath + ":" diff --git a/vadl/main/vadl/vdt/impl/irregular/IrregularDecodeTreeGenerator.java b/vadl/main/vadl/vdt/impl/irregular/IrregularDecodeTreeGenerator.java index 383431920..12ed4b368 100644 --- a/vadl/main/vadl/vdt/impl/irregular/IrregularDecodeTreeGenerator.java +++ b/vadl/main/vadl/vdt/impl/irregular/IrregularDecodeTreeGenerator.java @@ -26,8 +26,8 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -128,7 +128,7 @@ private Node makeNode(DecodeEntries decodeEntries) { return makeConditionNode(decodeEntries); } - final Map children = new HashMap<>(); + final Map children = new LinkedHashMap<>(); for (BitPattern p : patterns.patterns()) { final List matchingEntries = makeMatchingEntries(decodeEntries.entries(), p); diff --git a/vadl/main/vadl/vdt/passes/VdtConstraintSynthesisPass.java b/vadl/main/vadl/vdt/passes/VdtConstraintSynthesisPass.java index be3a9480d..436348413 100644 --- a/vadl/main/vadl/vdt/passes/VdtConstraintSynthesisPass.java +++ b/vadl/main/vadl/vdt/passes/VdtConstraintSynthesisPass.java @@ -22,8 +22,8 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -145,7 +145,7 @@ private boolean isActualSubset(Set superset, } private Map> groupedInstructions(List entries) { - final Map> result = new HashMap<>(); + final Map> result = new LinkedHashMap<>(); for (DecodeEntry e : entries) { var encoding = new InstructionEncoding(e, getFixedFields(e.source())); diff --git a/vadl/main/vadl/viam/DefinitionVisitor.java b/vadl/main/vadl/viam/DefinitionVisitor.java index 2d35941ff..cfe78f9a1 100644 --- a/vadl/main/vadl/viam/DefinitionVisitor.java +++ b/vadl/main/vadl/viam/DefinitionVisitor.java @@ -314,6 +314,7 @@ public void visit(MicroArchitecture microArchitecture) { microArchitecture.ownRegisters().forEach(register -> register.accept(this)); microArchitecture.ownMemories().forEach(memory -> memory.accept(this)); microArchitecture.ownFunctions().forEach(function -> function.accept(this)); + microArchitecture.isa().accept(this); afterTraversal(microArchitecture); } diff --git a/vadl/main/vadl/viam/Logic.java b/vadl/main/vadl/viam/Logic.java index a5492f7ea..c7d37769c 100644 --- a/vadl/main/vadl/viam/Logic.java +++ b/vadl/main/vadl/viam/Logic.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import vadl.types.Type; +import vadl.utils.Pair; import vadl.viam.graph.Graph; import vadl.viam.graph.dependency.ReadResourceNode; import vadl.viam.graph.dependency.WriteRegTensorNode; @@ -154,6 +155,8 @@ public static class Forwarding extends Logic { private final Map enable = new HashMap<>(); + private final Map, Signal> enableFrom = new HashMap<>(); + public Forwarding(Identifier identifier) { super(identifier); } @@ -182,6 +185,32 @@ public Signal getEnable(ReadResourceNode node) { return enable.get(node); } + /** + * Add a forward enable signal for a read node and a source stage to the forwarding logic. + * + * @param node read node + * @param stage stage forwarding from + * @param signal forward enable signal for read node and source stage + */ + public void putEnableFrom(ReadResourceNode node, Stage stage, Signal signal) { + enableFrom.put(Pair.of(node, stage), signal); + if (!signals().contains(signal)) { + signals().add(signal); + } + } + + /** + * Get the forward enable signal for a read node and a source stage. + * + * @param node read node + * @param stage stage forwarding from + * @return forward enable signal for read node and source stage + */ + @Nullable + public Signal getEnableFrom(ReadResourceNode node, Stage stage) { + return enableFrom.get(Pair.of(node, stage)); + } + } /** @@ -194,4 +223,27 @@ public BranchPrediction(Identifier identifier) { } } + + /** + * Logic definition for RVFI logic for formal verification. + */ + public static class RVFI extends Logic { + + public RVFI(Identifier identifier) { + super(identifier); + } + + /** + * Get signals the RVFI logic outputs. + * + * @return signals this logic writes to but not contains. + */ + public List outputSignals() { + return behavior().getNodes(WriteSignalNode.class) + .map(WriteSignalNode::signal) + .filter(signals()::contains) + .toList(); + } + + } } diff --git a/vadl/main/vadl/viam/Specification.java b/vadl/main/vadl/viam/Specification.java index 9001fae1c..8fd70f8c5 100644 --- a/vadl/main/vadl/viam/Specification.java +++ b/vadl/main/vadl/viam/Specification.java @@ -54,7 +54,8 @@ public Optional isa() { .map(InstructionSetArchitecture.class::cast) .findFirst(); if (isaDef.isEmpty()) { - return processor().map(Processor::isa); + return processor().map(Processor::isa) + .or(() -> mia().map(MicroArchitecture::isa)); } return isaDef; } diff --git a/vadl/main/vadl/viam/Stage.java b/vadl/main/vadl/viam/Stage.java index 51f67f4ce..0a31cc2b2 100644 --- a/vadl/main/vadl/viam/Stage.java +++ b/vadl/main/vadl/viam/Stage.java @@ -72,6 +72,16 @@ public Stage(Identifier identifier, Graph behavior, List outputs) { this.registers = new ArrayList<>(); this.localNames = new HashSet<>(); + // TODO this should be handled by the frontend (only add stage outputs from definition) + // currently the passed stage output list is always empty, it could contain the list of + // outputs give in the spec + this.behavior.getNodes(WriteStageOutputNode.class) + .map(WriteStageOutputNode::stageOutput).forEach(output -> { + if (!this.outputs.contains(output)) { + this.outputs.add(output); + } + }); + this.behavior.setParentDefinition(this); } diff --git a/vadl/main/vadl/viam/graph/dependency/MiaBuiltInCall.java b/vadl/main/vadl/viam/graph/dependency/MiaBuiltInCall.java index 7afec8ef4..ea403658c 100644 --- a/vadl/main/vadl/viam/graph/dependency/MiaBuiltInCall.java +++ b/vadl/main/vadl/viam/graph/dependency/MiaBuiltInCall.java @@ -23,6 +23,7 @@ import vadl.types.Type; import vadl.viam.Logic; import vadl.viam.Resource; +import vadl.viam.graph.Node; import vadl.viam.graph.NodeList; /** @@ -55,6 +56,35 @@ public MiaBuiltInCall(BuiltInTable.BuiltIn builtIn, NodeList arg ensure(BuiltInTable.MIA_BUILTINS.contains(builtIn), "Not a micro architecture builtin"); } + /** + * Create MiA builtin call. + * + * @param builtIn builtin type, must be in {@link BuiltInTable#MIA_BUILTINS} + * @param args arguments + * @param type result type + * @param resources resources referenced + * @param logic logic elements referenced + */ + public MiaBuiltInCall(BuiltInTable.BuiltIn builtIn, NodeList args, Type type, + List resources, List logic) { + super(builtIn, args, type); + this.resources = new ArrayList<>(resources); + this.logic = new ArrayList<>(logic); + ensure(BuiltInTable.MIA_BUILTINS.contains(builtIn), "Not a micro architecture builtin"); + } + + @Override + public MiaBuiltInCall copy() { + return new MiaBuiltInCall(builtIn, + new NodeList<>(this.arguments().stream().map(ExpressionNode::copy).toList()), + this.type(), resources, logic); + } + + @Override + public MiaBuiltInCall shallowCopy() { + return new MiaBuiltInCall(builtIn, args, type(), resources, logic); + } + @Override protected void collectData(List collection) { super.collectData(collection); diff --git a/vadl/main/vadl/viam/graph/dependency/StageEffectNode.java b/vadl/main/vadl/viam/graph/dependency/StageEffectNode.java index d0636bedd..e78bcf1da 100644 --- a/vadl/main/vadl/viam/graph/dependency/StageEffectNode.java +++ b/vadl/main/vadl/viam/graph/dependency/StageEffectNode.java @@ -40,6 +40,10 @@ public StageEffectNode(MiaBuiltInCall miaCall) { this.miaCall = miaCall; } + public MiaBuiltInCall miaCall() { + return miaCall; + } + @Override protected void collectData(List collection) { super.collectData(collection); diff --git a/vadl/main/vadl/viam/graph/dependency/WriteSignalNode.java b/vadl/main/vadl/viam/graph/dependency/WriteSignalNode.java index 918752736..7a82eb2f3 100644 --- a/vadl/main/vadl/viam/graph/dependency/WriteSignalNode.java +++ b/vadl/main/vadl/viam/graph/dependency/WriteSignalNode.java @@ -17,11 +17,13 @@ package vadl.viam.graph.dependency; import java.util.List; +import javax.annotation.Nullable; import vadl.javaannotations.viam.DataValue; import vadl.viam.Resource; import vadl.viam.Signal; import vadl.viam.graph.GraphNodeVisitor; import vadl.viam.graph.Node; +import vadl.viam.graph.NodeList; /** * Representing writing/driving the value of a signal. Any single signal can only have one driver. @@ -32,7 +34,11 @@ public class WriteSignalNode extends WriteResourceNode { protected Signal signal; public WriteSignalNode(Signal signal, ExpressionNode value) { - super(null, value); + this(signal, value, null); + } + + public WriteSignalNode(Signal signal, ExpressionNode value, @Nullable ExpressionNode condition) { + super(new NodeList<>(), value, condition); this.signal = signal; } @@ -61,12 +67,12 @@ public Signal signal() { @Override public Node copy() { - return new WriteSignalNode(signal, value.copy()); + return new WriteSignalNode(signal, value.copy(), (condition != null) ? condition.copy() : null); } @Override public Node shallowCopy() { - return new WriteSignalNode(signal, value); + return new WriteSignalNode(signal, value, condition); } @Override diff --git a/vadl/main/vadl/viam/matching/Matcher.java b/vadl/main/vadl/viam/matching/Matcher.java index 0dfdc0ac4..1c008e24a 100644 --- a/vadl/main/vadl/viam/matching/Matcher.java +++ b/vadl/main/vadl/viam/matching/Matcher.java @@ -18,6 +18,7 @@ import vadl.viam.graph.Node; import vadl.viam.matching.impl.BuiltInMatcher; +import vadl.viam.matching.impl.NegMatcher; /** * Interfaces to check the structure of a {@link Node}. @@ -37,4 +38,13 @@ public interface Matcher { default Matcher swapOperands() { throw new RuntimeException("Swapping operands is not allowed"); } + + /** + * Return a {@link NegMatcher} wrapping this matcher. + * + * @return matcher that gives the negated result of this matcher + */ + default Matcher not() { + return new NegMatcher(this); + } } diff --git a/vadl/main/vadl/viam/matching/impl/NegMatcher.java b/vadl/main/vadl/viam/matching/impl/NegMatcher.java new file mode 100644 index 000000000..63af4d1ea --- /dev/null +++ b/vadl/main/vadl/viam/matching/impl/NegMatcher.java @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText : © 2026 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.viam.matching.impl; + +import vadl.viam.graph.Node; +import vadl.viam.matching.Matcher; + +/** + * Negated matcher. Wraps a matcher and returns true, if it would return false. + */ +public class NegMatcher implements Matcher { + + private final Matcher matcher; + + public NegMatcher(Matcher matcher) { + this.matcher = matcher; + } + + @Override + public boolean matches(Node node) { + return !matcher.matches(node); + } + + @Override + public Matcher swapOperands() { + return new NegMatcher(matcher.swapOperands()); + } + + @Override + public Matcher not() { + return matcher; + } +} diff --git a/vadl/main/vadl/viam/passes/dummyPasses/DummyMiaPass.java b/vadl/main/vadl/viam/passes/dummyPasses/DummyMiaPass.java deleted file mode 100644 index 5e1a75e5f..000000000 --- a/vadl/main/vadl/viam/passes/dummyPasses/DummyMiaPass.java +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText : © 2025 TU Wien -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package vadl.viam.passes.dummyPasses; - -import java.io.IOException; -import javax.annotation.Nullable; -import vadl.configuration.GeneralConfiguration; -import vadl.configuration.RtlConfiguration; -import vadl.pass.Pass; -import vadl.pass.PassName; -import vadl.pass.PassResults; -import vadl.viam.Specification; - -/** - * Adds a hardcoded {@link vadl.viam.MicroArchitecture} definition to the VIAM specification. - * This is deleted as soon as the frontend can handle the translation. - * - *

If the supplied configuration is a {@link RtlConfiguration} the dummy MiA is selected based - * on the configuration. Otherwise, the default five stage pipeline is selected. - */ -public class DummyMiaPass extends Pass { - - public DummyMiaPass(GeneralConfiguration configuration) { - super(configuration); - } - - @Override - public PassName getName() { - return new PassName("Dummy Micro Architecture"); - } - - @Override - public @Nullable Object execute(PassResults passResults, Specification viam) - throws IOException { - - if (viam.mia().isPresent()) { - return null; - } - - var isa = viam.isa().orElse(null); - - if (isa == null) { - // if there is no mip, we just do nothing - return null; - } - - var dummyMia = RtlConfiguration.DummyMia.five; - if (configuration() instanceof RtlConfiguration rtlConfig) { - dummyMia = rtlConfig.getDummyMia(); - } - - viam.add( - switch (dummyMia) { - case single -> SingleStageDummyMia.mia(isa); - case three -> ThreeStageDummyMia.mia(viam, isa); - case five -> FiveStageDummyMia.mia(viam, isa); - } - ); - - viam.verify(); - - return null; - } - -} diff --git a/vadl/main/vadl/viam/passes/dummyPasses/FiveStageDummyMia.java b/vadl/main/vadl/viam/passes/dummyPasses/FiveStageDummyMia.java deleted file mode 100644 index e73976573..000000000 --- a/vadl/main/vadl/viam/passes/dummyPasses/FiveStageDummyMia.java +++ /dev/null @@ -1,273 +0,0 @@ -// SPDX-FileCopyrightText : © 2025 TU Wien -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package vadl.viam.passes.dummyPasses; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import javax.annotation.Nullable; -import vadl.types.BuiltInTable; -import vadl.types.MicroArchitectureType; -import vadl.types.Type; -import vadl.viam.Identifier; -import vadl.viam.InstructionSetArchitecture; -import vadl.viam.Logic; -import vadl.viam.Memory; -import vadl.viam.MicroArchitecture; -import vadl.viam.RegisterTensor; -import vadl.viam.Resource; -import vadl.viam.Specification; -import vadl.viam.Stage; -import vadl.viam.StageOutput; -import vadl.viam.graph.Graph; -import vadl.viam.graph.NodeList; -import vadl.viam.graph.dependency.MiaBuiltInCall; -import vadl.viam.graph.dependency.ReadStageOutputNode; -import vadl.viam.graph.dependency.WriteStageOutputNode; - -/** - * Five stage dummy MiA description. - * - *

This models a classical RISC pipeline with stages Fetch, Decode, Execute, Memory - * and Write Back. - */ -class FiveStageDummyMia { - - public static MicroArchitecture mia(Specification viam, InstructionSetArchitecture isa) { - var regFile = viam.isa().orElseThrow().registerTensors() - .stream().filter(RegisterTensor::isRegisterFile).findFirst().get(); - var mem = viam.isa().orElseThrow().ownMemories().get(0); - var pc = Objects.requireNonNull(isa.pc()).registerTensor(); - var csr = isa.registerTensors().stream() - .filter(reg -> reg.simpleName().equals("CSR")).findAny().orElse(null); - - var ident = Identifier.noLocation("MiA"); - var bypass = bypass(ident); - var predict = predict(ident); - - var fetch = fetch(ident.append("FETCH")); - var decode = decode(ident.append("DECODE"), fetch.outputs().get(0), regFile, bypass); - var execute = exec(ident.append("EXECUTE"), decode.outputs().get(0), pc, regFile, csr, - bypass); - var memory = memory(ident.append("MEMORY"), execute.outputs().get(0), mem); - var writeBack = writeBack(ident.append("WRITE_BACK"), memory.outputs().get(0), regFile); - - return new MicroArchitecture( - ident, - isa, - new ArrayList<>(List.of(fetch, decode, execute, memory, writeBack)), - new ArrayList<>(List.of(bypass, predict)) - ); - } - - private static StageOutput stageOutput(Identifier ident, Type type) { - return new StageOutput(ident, type); - } - - /** - *

-   * [forwarding]
-   * logic bypass
-   * 
. - */ - private static Logic.Forwarding bypass(Identifier parent) { - var id = parent.append("bypass"); - return new Logic.Forwarding(id); - } - - /** - *
-   * [branch predictor]
-   * logic predict
-   * 
. - */ - private static Logic.BranchPrediction predict(Identifier parent) { - var id = parent.append("predict"); - return new Logic.BranchPrediction(id); - } - - /** - *
-   * stage FETCH -> ( fr : FetchResult ) =
-   * {
-   *   fr := fetchNext
-   * }
-   * 
. - */ - private static Stage fetch(Identifier ident) { - var fr = stageOutput(ident.append("fr"), MicroArchitectureType.fetchResult()); - return new Stage(ident, fetchBehavior(fr), List.of(fr)); - } - - private static Graph fetchBehavior(StageOutput fr) { - var beh = new Graph("FETCH"); - var fn = new MiaBuiltInCall(BuiltInTable.FETCH_NEXT, new NodeList<>(), - MicroArchitectureType.fetchResult()); - var wr = new WriteStageOutputNode(fr, fn); - beh.addWithInputs(wr); - return beh; - } - - /** - *
-   * stage DECODE -> ( ir : Instruction ) =
-   * {
-   *   let instr = decode( FETCH.fr ) in
-   *   {
-   *     instr.address( @X )
-   *     instr.readOrForward( @X, @bypass )
-   *     ir := instr
-   *   }
-   * }
-   * 
. - */ - private static Stage decode(Identifier ident, StageOutput fetchIr, RegisterTensor regFile, - Logic bypass) { - var ir = stageOutput(ident.append("ir"), MicroArchitectureType.instruction()); - return new Stage(ident, decodeBehavior(fetchIr, ir, regFile, bypass), List.of(ir)); - } - - private static Graph decodeBehavior(StageOutput fetchIr, StageOutput ir, RegisterTensor regFile, - Logic bypass) { - var rd = new ReadStageOutputNode(fetchIr); - var i1 = new MiaBuiltInCall(BuiltInTable.DECODE, new NodeList<>(rd), - MicroArchitectureType.instruction()); - var i2 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_ADDRESS, new NodeList<>(i1), - MicroArchitectureType.instruction()); - i2.add(regFile); - var i3 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_READ_OR_FORWARD, new NodeList<>(i2), - MicroArchitectureType.instruction()); - i3.add(regFile); - i3.add(bypass); - var wr = new WriteStageOutputNode(ir, i3); - var beh = new Graph("DECODE"); - beh.addWithInputs(wr); - return beh; - } - - /** - *
-   * stage EXECUTE -> ( ir : Instruction ) =
-   * {
-   *   let instr = DECODE.ir in
-   *   {
-   *     instr.read( @PC, @CSR )
-   *     instr.compute
-   *     instr.verify
-   *     instr.write( @PC, @CSR )
-   *     instr.results( @X, @bypass )
-   *     ir := instr
-   *   }
-   * }
-   * 
. - */ - private static Stage exec(Identifier ident, StageOutput decodeIr, Resource pc, - RegisterTensor regFile, @Nullable RegisterTensor csr, Logic bypass) { - var ir = stageOutput(ident.append("ir"), MicroArchitectureType.instruction()); - return new Stage(ident, executeBehavior(decodeIr, ir, pc, regFile, csr, bypass), - List.of(ir)); - } - - private static Graph executeBehavior(StageOutput decodeIr, StageOutput ir, Resource pc, - RegisterTensor regFile, @Nullable RegisterTensor csr, - Logic bypass) { - var rd = new ReadStageOutputNode(decodeIr); - var i1 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_READ, new NodeList<>(rd), - MicroArchitectureType.instruction()); - i1.add(pc); - if (csr != null) { - i1.add(csr); - } - var i2 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_COMPUTE, new NodeList<>(i1), - MicroArchitectureType.instruction()); - var i3 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_VERIFY, new NodeList<>(i2), - MicroArchitectureType.instruction()); - var i4 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_WRITE, new NodeList<>(i3), - MicroArchitectureType.instruction()); - i4.add(pc); - if (csr != null) { - i4.add(csr); - } - var i5 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_RESULTS, new NodeList<>(i4), - MicroArchitectureType.instruction()); - i5.add(regFile); - i5.add(bypass); - var wr = new WriteStageOutputNode(ir, i5); - var beh = new Graph("EXECUTE"); - beh.addWithInputs(wr); - return beh; - } - - /** - *
-   * stage MEMORY -> ( ir : Instruction ) =
-   * {
-   *   let instr = EXECUTE.ir in
-   *   {
-   *     instr.write( @MEM )
-   *     instr.read( @MEM )
-   *     ir := instr
-   *   }
-   * }
-   * 
. - */ - private static Stage memory(Identifier ident, StageOutput executeIr, Memory mem) { - var ir = stageOutput(ident.append("ir"), MicroArchitectureType.instruction()); - return new Stage(ident, memoryBehavior(executeIr, ir, mem), List.of(ir)); - } - - private static Graph memoryBehavior(StageOutput executeIr, StageOutput ir, Memory mem) { - var beh = new Graph("MEMORY"); - var rd = new ReadStageOutputNode(executeIr); - var i1 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_WRITE, new NodeList<>(rd), - MicroArchitectureType.instruction()); - i1.add(mem); - var i2 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_READ, new NodeList<>(i1), - MicroArchitectureType.instruction()); - i2.add(mem); - var wr = new WriteStageOutputNode(ir, i2); - beh.addWithInputs(wr); - return beh; - } - - /** - *
-   * stage WRITE_BACK =
-   * {
-   *   let instr = MEMORY.ir in
-   *   {
-   *     instr.write( @X )
-   *   }
-   * }
-   * 
. - */ - private static Stage writeBack(Identifier ident, StageOutput memoryIr, RegisterTensor regFile) { - return new Stage(ident, writeBackBehavior(memoryIr, regFile), List.of()); - } - - private static Graph writeBackBehavior(StageOutput memoryIr, RegisterTensor regFile) { - var beh = new Graph("WRITE_BACK"); - var rd = new ReadStageOutputNode(memoryIr); - var i1 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_WRITE, new NodeList<>(rd), - MicroArchitectureType.instruction()); - i1.add(regFile); - var wr = new WriteStageOutputNode(null, i1); - beh.addWithInputs(wr); - return beh; - } - -} diff --git a/vadl/main/vadl/viam/passes/dummyPasses/SingleStageDummyMia.java b/vadl/main/vadl/viam/passes/dummyPasses/SingleStageDummyMia.java deleted file mode 100644 index f806a822e..000000000 --- a/vadl/main/vadl/viam/passes/dummyPasses/SingleStageDummyMia.java +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText : © 2025 TU Wien -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package vadl.viam.passes.dummyPasses; - -import java.util.ArrayList; -import java.util.List; -import vadl.types.BuiltInTable; -import vadl.types.MicroArchitectureType; -import vadl.types.Type; -import vadl.viam.Identifier; -import vadl.viam.InstructionSetArchitecture; -import vadl.viam.MicroArchitecture; -import vadl.viam.Stage; -import vadl.viam.StageOutput; -import vadl.viam.graph.Graph; -import vadl.viam.graph.NodeList; -import vadl.viam.graph.dependency.MiaBuiltInCall; -import vadl.viam.graph.dependency.WriteStageOutputNode; - -/** - * Single stage dummy MiA description. - * - *

Placing the whole instruction behavior in a single pipeline stage may require the stage to - * take more than one cycle for execution. In this case an implementation could share resources - * between the execution steps (which our hardware generation currently does not do). - */ -class SingleStageDummyMia { - - public static MicroArchitecture mia(InstructionSetArchitecture isa) { - var ident = Identifier.noLocation("MiA"); - - var issStage = iss(ident.append("ISS")); - - return new MicroArchitecture( - ident, - isa, - new ArrayList<>(List.of(issStage)), - new ArrayList<>() - ); - } - - private static StageOutput stageOutput(Identifier ident, Type type) { - return new StageOutput(ident, type); - } - - /** - *

-   * stage ISS -> ( ir : Instruction ) =
-   * {
-   *   let instr = decode ( fetchNext ) in
-   *   {
-   *     instr.write
-   *     ir := instr
-   *   }
-   * }
-   * 
. - */ - private static Stage iss(Identifier ident) { - var ir = stageOutput(ident.append("fr"), MicroArchitectureType.instruction()); - return new Stage(ident, issBehavior(ir), List.of(ir)); - } - - private static Graph issBehavior(StageOutput ir) { - var beh = new Graph("ISS"); - var fn = new MiaBuiltInCall(BuiltInTable.FETCH_NEXT, new NodeList<>(), - MicroArchitectureType.fetchResult()); - var i1 = new MiaBuiltInCall(BuiltInTable.DECODE, new NodeList<>(fn), - MicroArchitectureType.instruction()); - var i2 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_WRITE, new NodeList<>(i1), - MicroArchitectureType.instruction()); - var wr = new WriteStageOutputNode(ir, i2); - beh.addWithInputs(wr); - return beh; - } - -} diff --git a/vadl/main/vadl/viam/passes/dummyPasses/ThreeStageDummyMia.java b/vadl/main/vadl/viam/passes/dummyPasses/ThreeStageDummyMia.java deleted file mode 100644 index bf92054af..000000000 --- a/vadl/main/vadl/viam/passes/dummyPasses/ThreeStageDummyMia.java +++ /dev/null @@ -1,190 +0,0 @@ -// SPDX-FileCopyrightText : © 2025 TU Wien -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package vadl.viam.passes.dummyPasses; - -import java.util.ArrayList; -import java.util.List; -import vadl.types.BuiltInTable; -import vadl.types.MicroArchitectureType; -import vadl.types.Type; -import vadl.viam.Identifier; -import vadl.viam.InstructionSetArchitecture; -import vadl.viam.Logic; -import vadl.viam.Memory; -import vadl.viam.MicroArchitecture; -import vadl.viam.RegisterTensor; -import vadl.viam.Specification; -import vadl.viam.Stage; -import vadl.viam.StageOutput; -import vadl.viam.graph.Graph; -import vadl.viam.graph.NodeList; -import vadl.viam.graph.dependency.MiaBuiltInCall; -import vadl.viam.graph.dependency.ReadStageOutputNode; -import vadl.viam.graph.dependency.WriteStageOutputNode; - -/** - * Three stage dummy MiA description. - * - *

This models a pipeline with stages Instruction Fetch, Instruction Decode and Execute following - * the design of the Wildcat educational processor. - * - *

Schoeberl, M. (2025). Wildcat: Educational RISC-V Microprocessors. arXiv. - * Paper. - */ -class ThreeStageDummyMia { - - public static MicroArchitecture mia(Specification viam, InstructionSetArchitecture isa) { - var regFile = viam.isa().orElseThrow().registerTensors() - .stream().filter(RegisterTensor::isRegisterFile).findFirst().get(); - var mem = viam.isa().orElseThrow().ownMemories().get(0); - - var ident = Identifier.noLocation("MiA"); - var bypass = bypass(ident); - var predict = predict(ident); - - var ifStage = ifStage(ident.append("IF")); - var id = id(ident.append("ID"), ifStage.outputs().get(0), regFile, bypass); - var ex = ex(ident.append("EX"), id.outputs().get(0), regFile, mem, bypass); - - return new MicroArchitecture( - ident, - isa, - new ArrayList<>(List.of(ifStage, id, ex)), - new ArrayList<>(List.of(bypass, predict)) - ); - } - - private static StageOutput stageOutput(Identifier ident, Type type) { - return new StageOutput(ident, type); - } - - /** - *

-   * [forwarding]
-   * logic bypass
-   * 
. - */ - private static Logic.Forwarding bypass(Identifier parent) { - var id = parent.append("bypass"); - return new Logic.Forwarding(id); - } - - /** - *
-   * [branch predictor]
-   * logic predict
-   * 
. - */ - private static Logic.BranchPrediction predict(Identifier parent) { - var id = parent.append("predict"); - return new Logic.BranchPrediction(id); - } - - /** - *
-   * stage IF -> ( fr : FetchResult ) =
-   * {
-   *   fr := fetchNext
-   * }
-   * 
. - */ - private static Stage ifStage(Identifier ident) { - var fr = stageOutput(ident.append("fr"), MicroArchitectureType.fetchResult()); - return new Stage(ident, ifBehavior(fr), List.of(fr)); - } - - private static Graph ifBehavior(StageOutput fr) { - var beh = new Graph("IF"); - var fn = new MiaBuiltInCall(BuiltInTable.FETCH_NEXT, new NodeList<>(), - MicroArchitectureType.fetchResult()); - var wr = new WriteStageOutputNode(fr, fn); - beh.addWithInputs(wr); - return beh; - } - - /** - *
-   * stage ID -> ( ir : Instruction ) =
-   * {
-   *   let instr = decode( IF.fr ) in
-   *   {
-   *     instr.readOrForward( @X, @bypass )
-   *     ir := instr
-   *   }
-   * }
-   * 
. - */ - private static Stage id(Identifier ident, StageOutput ifFr, RegisterTensor regFile, - Logic bypass) { - var ir = stageOutput(ident.append("ir"), MicroArchitectureType.instruction()); - return new Stage(ident, idBehavior(ifFr, ir, regFile, bypass), List.of(ir)); - } - - private static Graph idBehavior(StageOutput ifFr, StageOutput ir, RegisterTensor regFile, - Logic bypass) { - var rd = new ReadStageOutputNode(ifFr); - var i1 = new MiaBuiltInCall(BuiltInTable.DECODE, new NodeList<>(rd), - MicroArchitectureType.instruction()); - var i2 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_READ_OR_FORWARD, new NodeList<>(i1), - MicroArchitectureType.instruction()); - i2.add(regFile); - i2.add(bypass); - var wr = new WriteStageOutputNode(ir, i2); - var beh = new Graph("ID"); - beh.addWithInputs(wr); - return beh; - } - - /** - *
-   * stage EX -> ( ir : Instruction ) =
-   * {
-   *   let instr = ID.ir in
-   *   {
-   *     instr.results( @X, @bypass )
-   *     instr.read( @MEM )
-   *     instr.write
-   *     ir := instr
-   *   }
-   * }
-   * 
. - */ - private static Stage ex(Identifier ident, StageOutput idIr, RegisterTensor regFile, Memory mem, - Logic bypass) { - var ir = stageOutput(ident.append("ir"), MicroArchitectureType.instruction()); - return new Stage(ident, exBehavior(idIr, ir, regFile, mem, bypass), List.of(ir)); - } - - private static Graph exBehavior(StageOutput idIr, StageOutput ir, RegisterTensor regFile, - Memory mem, Logic bypass) { - var rd = new ReadStageOutputNode(idIr); - var i1 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_RESULTS, new NodeList<>(rd), - MicroArchitectureType.instruction()); - i1.add(regFile); - i1.add(bypass); - var i2 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_READ, new NodeList<>(i1), - MicroArchitectureType.instruction()); - i2.add(mem); - var i3 = new MiaBuiltInCall(BuiltInTable.INSTRUCTION_WRITE, new NodeList<>(i2), - MicroArchitectureType.instruction()); - var wr = new WriteStageOutputNode(ir, i3); - var beh = new Graph("EX"); - beh.addWithInputs(wr); - return beh; - } - -} diff --git a/vadl/main/vadl/viam/passes/sideeffect_condition/SideEffectConditionResolvingPass.java b/vadl/main/vadl/viam/passes/sideeffect_condition/SideEffectConditionResolvingPass.java index 36987020f..ae4913fe1 100644 --- a/vadl/main/vadl/viam/passes/sideeffect_condition/SideEffectConditionResolvingPass.java +++ b/vadl/main/vadl/viam/passes/sideeffect_condition/SideEffectConditionResolvingPass.java @@ -26,6 +26,7 @@ import vadl.viam.Instruction; import vadl.viam.Procedure; import vadl.viam.Specification; +import vadl.viam.Stage; /** * A pass that finds all instructions and procedures in the specification and @@ -61,6 +62,12 @@ public PassName getName() { SideEffectConditionResolver.run(((Procedure) procedure).behavior()); } + var stages = ViamUtils + .findDefinitionsByFilter(viam, d -> d instanceof Stage); + for (var stage : stages) { + SideEffectConditionResolver.run(((Stage) stage).behavior()); + } + return null; } } diff --git a/vadl/test/resources/scripts/rtl/test.sh b/vadl/test/resources/scripts/rtl/test.sh index 4d44fc827..521d3ae3f 100755 --- a/vadl/test/resources/scripts/rtl/test.sh +++ b/vadl/test/resources/scripts/rtl/test.sh @@ -3,9 +3,8 @@ cd /rtl mkdir -p build -touch build/rv32i-riscv-tests.log # Preserve sbt exit code when piping to tee set -o pipefail -sbt test 2>&1 | tee build/rv32i-riscv-tests.log +sbt test 2>&1 | tee build/test.log diff --git a/vadl/test/vadl/AbstractTest.java b/vadl/test/vadl/AbstractTest.java index a18a6b449..200973123 100644 --- a/vadl/test/vadl/AbstractTest.java +++ b/vadl/test/vadl/AbstractTest.java @@ -30,6 +30,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -147,10 +148,11 @@ public static Path getTestSourcePath(String path) { */ public static Stream getTestSourceArgsForParameterizedTest(String sourcePrefix, Arguments... args) { + var cleanPrefix = sourcePrefix.replace("/", FileSystems.getDefault().getSeparator()); var testSources = findAllTestSources(sourcePrefix); var preparedArgs = Stream.of(args).map(e -> { assertEquals(2, e.get().length, "Wrong number of arguments for " + e); - return arguments(sourcePrefix + e.get()[0] + ".vadl", e.get()[1]); + return arguments(cleanPrefix + e.get()[0] + ".vadl", e.get()[1]); }).toList(); List expectedSubstrings = preparedArgs.stream().map(e -> (String) e.get()[0]).toList(); @@ -170,6 +172,7 @@ public static Stream getTestSourceArgsForParameterizedTest(String sou * @throws RuntimeException if an IO exception occurs */ public static List findAllTestSources(String prefix) { + prefix = prefix.replace("/", FileSystems.getDefault().getSeparator()); var resourceUrl = Objects.requireNonNull( // just get some class for resource fetching frontendProvider.getClass().getResource("/" + TEST_SOURCE_DIR + "/")); diff --git a/vadl/test/vadl/TestUtils.java b/vadl/test/vadl/TestUtils.java index 869506159..e86d7b0ef 100644 --- a/vadl/test/vadl/TestUtils.java +++ b/vadl/test/vadl/TestUtils.java @@ -120,17 +120,19 @@ public static Specification compileToViam(String sourceCode) { /** * Asserts that the File referenced with the provided Path contains equals the actual string. * This test is optimized for IntelliJ and by throwing a custom AssertionFailedError the IDE will - * show a diff editor in which the user can accept the changes into the referenced filed. + * show a diff editor in which the user can accept the changes into the referenced file. + * We compare the content line-by-line in order to ignore different line endings in different + * environments. * * @param expectedPath the file to compare the actual string with * @param actual the actual string the test produced. * @throws IOException if the file cannot be read. */ - public static void assertEqualsFileContent(Path expectedPath, String actual) + public static void assertEqualsFileLines(Path expectedPath, String actual) throws IOException { var expected = Files.readString(expectedPath); - if (!expected.equals(actual)) { + if (!expected.lines().toList().equals(actual.lines().toList())) { throw new AssertionFailedError( "Actual data differs from file.", new FileInfo(expectedPath.toAbsolutePath().toString(), diff --git a/vadl/test/vadl/ast/DiagnosticsTest.java b/vadl/test/vadl/ast/DiagnosticsTest.java index 1ac29fc84..364ee107c 100644 --- a/vadl/test/vadl/ast/DiagnosticsTest.java +++ b/vadl/test/vadl/ast/DiagnosticsTest.java @@ -16,7 +16,7 @@ package vadl.ast; -import static vadl.TestUtils.assertEqualsFileContent; +import static vadl.TestUtils.assertEqualsFileLines; import static vadl.ast.AstTestUtils.verifyPrettifiedAst; import java.io.IOException; @@ -89,10 +89,11 @@ void runSnapshotTest(Path path) throws IOException { // FIXME: Maybe deferred diagnostic store here but add later because it might have problems // being global state not reset by the test suite. - // Force relative paths because the tests must always produce the same and the absolut path - // will differ on different machines. + // Force relative and slashified paths because the tests must always produce the same and the + // absolute path and file separator will differ on different machines. DiagnosticPrinter printer = new DiagnosticPrinter(false); printer.forceRelativePaths = true; + printer.forceUnixPaths = true; var output = "Reported Diagnostics:\n\n"; output += !diagnostics.isEmpty() ? printer.toString(diagnostics).stripTrailing() : "No diagnostics were reported, the input was correctly parsed, typechecked and lowered."; @@ -122,7 +123,7 @@ void runSnapshotTest(Path path) throws IOException { return; } - assertEqualsFileContent(path, actual); + assertEqualsFileLines(path, actual); } // Similar to the full version but it infers the name from the method name of the test. diff --git a/vadl/test/vadl/ast/FrontendIntegrationTest.java b/vadl/test/vadl/ast/FrontendIntegrationTest.java index ffaac20b3..47f42860e 100644 --- a/vadl/test/vadl/ast/FrontendIntegrationTest.java +++ b/vadl/test/vadl/ast/FrontendIntegrationTest.java @@ -38,6 +38,9 @@ public class FrontendIntegrationTest { "../sys/risc-v/rv64im.vadl", "../sys/risc-v/rv64v.vadl", "../sys/risc-v/rvcsr.vadl", + "../sys/risc-v/mia/rv_1stage.vadl", + "../sys/risc-v/mia/rv_3stage.vadl", + "../sys/risc-v/mia/rv_5stage.vadl", "../sys/v-risc/ABI.vadl" }) public void testFrontendPassingOnSysSpecs(String filename) { diff --git a/vadl/test/vadl/rtl/InstructionBehaviorCheckPass.java b/vadl/test/vadl/rtl/InstructionBehaviorCheckPass.java index fa11c048e..7f40ad808 100644 --- a/vadl/test/vadl/rtl/InstructionBehaviorCheckPass.java +++ b/vadl/test/vadl/rtl/InstructionBehaviorCheckPass.java @@ -44,6 +44,7 @@ import vadl.rtl.utils.SubgraphUtils; import vadl.types.Type; import vadl.viam.Constant; +import vadl.viam.MicroArchitecture; import vadl.viam.Resource; import vadl.viam.Specification; import vadl.viam.graph.Graph; @@ -88,7 +89,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - var isa = viam.isa().orElseThrow(); + var isa = viam.mia().map(MicroArchitecture::isa).orElseThrow(); var pc = Objects.requireNonNull(isa.pc()).registerTensor(); var ipg = isa.expectExtension(InstructionProgressGraphExtension.class).ipg(); @@ -98,7 +99,7 @@ public Object execute(PassResults passResults, Specification viam) throws IOExce for (var curInstr : isa.ownInstructions()) { if (curInstr.behavior().getNodes(ProcCallNode.class).findAny().isPresent()) { - continue; // skip for now, since procedure calls are inlined in ipg, skip for now + continue; // since procedure calls are inlined in ipg, skip for now } Graph graph; @@ -215,8 +216,8 @@ private void compare(Graph instrBeh, Graph graph, .filter(node -> !ignore.contains(node)) .filter(n -> filterResource(n, resource)).toList(); Assertions.assertEquals(countNodes(inInstr, resource), inGraph.size(), - "Number of " + typeIns.getSimpleName() + " nodes does not match number of " - + typeIpg.getSimpleName() + " nodes for resource " + resource); + "%s: Number of %s nodes does not match number of %s nodes for %s" + .formatted(instrBeh.name, typeIns.getSimpleName(), typeIpg.getSimpleName(), resource)); } private boolean filterResource(Node node, Resource resource) { diff --git a/vadl/test/vadl/rtl/PruneIsaPass.java b/vadl/test/vadl/rtl/PruneIsaPass.java new file mode 100644 index 000000000..395dfb793 --- /dev/null +++ b/vadl/test/vadl/rtl/PruneIsaPass.java @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.rtl; + +import java.io.IOException; +import java.util.Set; +import javax.annotation.Nullable; +import vadl.configuration.GeneralConfiguration; +import vadl.pass.Pass; +import vadl.pass.PassName; +import vadl.pass.PassResults; +import vadl.viam.RegisterTensor; +import vadl.viam.Specification; + +/** + * Pass for testing that removes VIAM elements to get a simple test case. + */ +public class PruneIsaPass extends Pass { + + private final Set instructions; + + private final boolean regTensorConstraints; + + /** + * New prune ISA pass that removes all instructions, but the ones referenced by a set of names. + * If the set is empty, all instructions are kept. + * + * @param config configuration + * @param instructions set of instruction names + */ + public PruneIsaPass(GeneralConfiguration config, Set instructions) { + super(config); + this.instructions = instructions; + this.regTensorConstraints = true; + } + + /** + * New prune ISA pass that removes all instructions, but the ones referenced by a set of names. + * If the set is empty, all instructions are kept. Optionally, remove register tensor constraints. + * + * @param config configuration + * @param instructions set of instruction names + * @param regTensorConstraints keep register tensor constraints, if true + */ + public PruneIsaPass(GeneralConfiguration config, Set instructions, + boolean regTensorConstraints) { + super(config); + this.instructions = instructions; + this.regTensorConstraints = regTensorConstraints; + } + + @Override + public PassName getName() { + return PassName.of("Prune ISA"); + } + + @Nullable + @Override + public Object execute(PassResults passResults, Specification viam) throws IOException { + viam.isa().ifPresent(isa -> { + if (!instructions.isEmpty()) { + isa.ownInstructions().removeIf(ins -> !instructions.contains(ins.simpleName())); + } + if (!regTensorConstraints) { + for (RegisterTensor regTensor : isa.registerTensors()) { + regTensor.setConstraints(); + } + } + }); + return null; + } + +} diff --git a/vadl/test/vadl/rtl/RtlEmitMinimizedRiscVTest.java b/vadl/test/vadl/rtl/RtlEmitMinimizedRiscVTest.java new file mode 100644 index 000000000..d82f34b4c --- /dev/null +++ b/vadl/test/vadl/rtl/RtlEmitMinimizedRiscVTest.java @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.rtl; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Set; +import org.junit.jupiter.api.Test; +import vadl.AbstractTest; +import vadl.configuration.DumpMode; +import vadl.configuration.GeneralConfiguration; +import vadl.configuration.RtlConfiguration; +import vadl.pass.PassOrders; +import vadl.pass.exception.DuplicatedPassKeyException; + +/** + * Simple test of the MiA synthesis and emitting RTL. + * Only keeps a few instructions to get a simpler output. + */ +public class RtlEmitMinimizedRiscVTest extends AbstractTest { + + private static final Set instructions = Set.of( + "ADD", "ADDI", "AUIPC", "LW", "SW", "BEQ", "JAL", "JALR" + ); + + @Test + void emitMinimizedRV32() throws IOException, DuplicatedPassKeyException { + var generalConfig = + new GeneralConfiguration(Path.of("build/test-output"), DumpMode.NONE); + + var config = new RtlConfiguration(generalConfig); + config.setMemory(RtlConfiguration.Memory.async); + config.setEmitDebugPrint(false); + + var order = PassOrders.rtl(config); + order.addAfterFirst(PassOrders.ViamCreationPass.class, new PruneIsaPass(config, instructions)); + + setupPassManagerAndRunSpec("sys/risc-v/mia/rv_5stage.vadl", order); + } + +} diff --git a/vadl/test/vadl/rtl/RtlLoweringTest.java b/vadl/test/vadl/rtl/RtlLoweringTest.java index 9ac3bbe77..8e9cca37c 100644 --- a/vadl/test/vadl/rtl/RtlLoweringTest.java +++ b/vadl/test/vadl/rtl/RtlLoweringTest.java @@ -39,6 +39,7 @@ import vadl.rtl.passes.InstructionProgressGraphMergePass; import vadl.rtl.passes.MiaMappingCreationPass; import vadl.rtl.passes.MiaMappingOptimizePass; +import vadl.viam.MicroArchitecture; import vadl.viam.RegisterTensor; import vadl.viam.Specification; @@ -70,7 +71,7 @@ void instructionBehaviorCheck() throws IOException, DuplicatedPassKeyException { addDumpAndCheck(config, order, MiaMappingOptimizePass.class); addDumpAndCheck(config, order, InstructionProgressGraphLowerPass.class, false); - setupPassManagerAndRunSpec("sys/risc-v/rv32i.vadl", order); + setupPassManagerAndRunSpec("sys/risc-v/mia/rv_5stage.vadl", order); } private void addDumpAndCheck(GeneralConfiguration config, PassOrder order, Class selector) { @@ -132,7 +133,7 @@ public PassName getName() { @Nullable @Override public Object execute(PassResults passResults, Specification viam) throws IOException { - viam.isa().ifPresent(isa -> { + viam.mia().map(MicroArchitecture::isa).ifPresent(isa -> { if (!instructions.isEmpty()) { isa.ownInstructions().removeIf(ins -> !instructions.contains(ins.simpleName())); } diff --git a/vadl/test/vadl/rtl/riscv/RiscVInstructionTest.java b/vadl/test/vadl/rtl/riscv/RiscVInstructionTest.java deleted file mode 100644 index 17d2b405f..000000000 --- a/vadl/test/vadl/rtl/riscv/RiscVInstructionTest.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText : © 2025-2026 TU Wien -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package vadl.rtl.riscv; - -import vadl.rtl.RtlDockerTest; - -public class RiscVInstructionTest extends RtlDockerTest { - - // TODO @linushdot: Reenable this test once all new MIA nodes are implemented, and prefix the - // class name with `Rtl`. - // https://github.com/OpenVADL/openvadl/issues/618 - // - // @Test - // void rv32imTest() { - // - // /* GIVEN */ - // var generalConfig = - // new GeneralConfiguration(Path.of("build/test-output"), DumpMode.NONE); - // var config = new RtlConfiguration(generalConfig); - // - // var decoderOptions = new DecoderOptions(); - // decoderOptions.setGenerator(DecoderOptions.Generator.REGULAR); - // config.setDecoderOptions(decoderOptions); - // - // var image = generateRtlImage("sys/risc-v/rv32im.vadl", config); - // - // runContainer(image, - // /* WHEN */ - // c -> c.withCommand("/scripts/test.sh"), - // /* THEN */ - // c -> { - // // No post actions for now, we rely on the exit code instead - // }); - // } - -} diff --git a/vadl/test/vadl/rtl/riscv/RtlRiscVInstructionTest.java b/vadl/test/vadl/rtl/riscv/RtlRiscVInstructionTest.java new file mode 100644 index 000000000..bcd5b759f --- /dev/null +++ b/vadl/test/vadl/rtl/riscv/RtlRiscVInstructionTest.java @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText : © 2025 TU Wien +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package vadl.rtl.riscv; + +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import vadl.configuration.DecoderOptions; +import vadl.configuration.DumpMode; +import vadl.configuration.GeneralConfiguration; +import vadl.configuration.RtlConfiguration; +import vadl.rtl.RtlDockerTest; + +public class RtlRiscVInstructionTest extends RtlDockerTest { + + @Test + void rv64imFiveTest() { + + /* GIVEN */ + var generalConfig = + new GeneralConfiguration(Path.of("build/test-output"), DumpMode.NONE); + var config = new RtlConfiguration(generalConfig); + config.setResetVector("reset_vector"); + + var decoderOptions = new DecoderOptions(); + decoderOptions.setGenerator(DecoderOptions.Generator.REGULAR); + config.setDecoderOptions(decoderOptions); + + var image = generateRtlImage("sys/risc-v/mia/rv_5stage.vadl", config); + + runContainer(image, + /* WHEN */ + c -> c.withCommand("/scripts/test.sh"), + /* THEN */ + c -> { + // No post actions for now, we rely on the exit code instead + }); + } + +}