diff --git a/Makefile b/Makefile index a67aae05dccabadbb649796cc7928749ca7e5956..cab6c71e15f74823f85715b8738cc093976ede4c 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,12 @@ VEXTRA_FLAGS += --savable EMU_CXXFLAGS += -DVM_SAVABLE endif +# Verilator coverage +EMU_COVERAGE ?= +ifeq ($(EMU_COVERAGE),1) +VEXTRA_FLAGS += --coverage-line --coverage-toggle +endif + # co-simulation with DRAMsim3 ifeq ($(WITH_DRAMSIM3),1) EMU_CXXFLAGS += -I$(DRAMSIM3_HOME)/src @@ -167,6 +173,11 @@ emu: $(EMU) ls build $(EMU) -i $(IMAGE) $(EMU_FLAGS) +coverage: + verilator_coverage --annotate build/logs/annotated --annotate-min 1 build/logs/coverage.dat + python3 scripts/coverage/coverage.py build/logs/annotated/XSSimTop.v build/XSSimTop_annotated.v + python3 scripts/coverage/statistics.py build/XSSimTop_annotated.v >build/coverage.log + # extract verilog module from sim_top.v # usage: make vme VME_MODULE=Roq vme: $(SIM_TOP_V) diff --git a/scripts/coverage/statistics.py b/scripts/coverage/statistics.py index 6b853bfd086daf13f9e105d278402c1bc1fccf28..0a875c1bba2150dcfcc06a330176c4777f490c00 100644 --- a/scripts/coverage/statistics.py +++ b/scripts/coverage/statistics.py @@ -3,26 +3,223 @@ import sys import re import copy +import pprint + +COVERRED = "COVERRED" +NOT_COVERRED = "NOT_COVERRED" +DONTCARE = "DONTCARE" +BEGIN = "BEGIN" +END = "END" +CHILDREN = "CHILDREN" +MODULE = "MODULE" +INSTANCE = "INSTANCE" +TYPE="TYPE" +ROOT="ROOT" +NODE="NODE" +SELFCOVERAGE="SELFCOVERAGE" +TREECOVERAGE="TREECOVERAGE" + +def get_lines(input_file): + lines = [] + with open(input_file) as f: + for line in f: + lines.append(line) + return lines + +def get_line_annotation(lines): + line_annotations = [] + # pattern_1: 040192 if(array_0_MPORT_en & array_0_MPORT_mask) begin + # pattern_2: 2218110 end else if (_T_30) begin // @[Conditional.scala 40:58] + # pattern_2: 000417 end else begin + coverred_pattern_1 = re.compile('^\s*(\d+)\s+if') + coverred_pattern_2 = re.compile('^\s*(\d+)\s+end else') + not_coverred_pattern_1 = re.compile('^\s*(%0+)\s+if') + not_coverred_pattern_2 = re.compile('^\s*(%0+)\s+end else') + + for line in lines: + coverred_match = coverred_pattern_1.search(line) or coverred_pattern_2.search(line) + not_coverred_match = not_coverred_pattern_1.search(line) or not_coverred_pattern_2.search(line) + + assert not (coverred_match and not_coverred_match) + + if coverred_match: + line_annotations.append(COVERRED) + elif not_coverred_match: + line_annotations.append(NOT_COVERRED) + else: + line_annotations.append(DONTCARE) + return line_annotations + +# get the line coverage statistics in line range [start, end) +def get_coverage_statistics(line_annotations, start, end): + coverred = 0 + not_coverred = 0 + for i in range(start, end): + if line_annotations[i] == COVERRED: + coverred += 1 + + if line_annotations[i] == NOT_COVERRED: + not_coverred += 1 + + # deal with divide by zero + coverage = 1.0 + if coverred + not_coverred != 0: + coverage = float(coverred) / (coverred + not_coverred) + return (coverred, not_coverred, coverage) + +# get modules and all it's submodules +def get_modules(lines): + modules = {} + + module_pattern = re.compile("module (\w+)\(") + endmodule_pattern = re.compile("endmodule") + submodule_pattern = re.compile("(\w+) (\w+) \( // @\[\w+.scala \d+:\d+\]") + + line_count = 0 + + name = "ModuleName" + + for line in lines: + module_match = module_pattern.search(line) + endmodule_match = endmodule_pattern.search(line) + submodule_match = submodule_pattern.search(line) + + assert not (module_match and endmodule_match) + + if module_match: + name = module_match.group(1) + # print("module_match: module: %s" % name) + assert name not in modules + # [begin + modules[name] = {} + modules[name][BEGIN] = line_count + # the first time we see a module, we treat as a root node + modules[name][TYPE] = ROOT + + if endmodule_match: + # print("endmodule_match: module: %s" % name) + assert name in modules + assert END not in modules[name] + # end) + modules[name][END] = line_count + 1 + # reset module name to invalid + name = "ModuleName" + + if submodule_match: + # submodule must be inside hierarchy + assert name != "ModuleName" + submodule_type = submodule_match.group(1) + submodule_instance = submodule_match.group(2) + # print("submodule_match: type: %s instance: %s" % (submodule_type, submodule_instance)) + + # submodules should be defined first + # if we can not find it's definition + # we consider it a black block module + if submodule_type not in modules: + print("Module %s is a Blackbox" % submodule_type) + else: + # mark submodule as a tree node + # it's no longer root any more + modules[submodule_type][TYPE] = NODE + + if CHILDREN not in modules[name]: + modules[name][CHILDREN] = [] + submodule = {MODULE: submodule_type, INSTANCE: submodule_instance} + modules[name][CHILDREN].append(submodule) + + line_count += 1 + return modules + +# we define two coverage metrics: +# self coverage: coverage results of this module(excluding submodules) +# tree coverage: coverage results of this module(including submodules) +def get_tree_coverage(modules, coverage): + def dfs(module): + if TREECOVERAGE not in modules[module]: + self_coverage = modules[module][SELFCOVERAGE] + if CHILDREN not in modules[module]: + modules[module][TREECOVERAGE] = self_coverage + else: + coverred = self_coverage[0] + not_coverred = self_coverage[1] + # the dfs part + for child in modules[module][CHILDREN]: + child_coverage = dfs(child[MODULE]) + coverred += child_coverage[0] + not_coverred += child_coverage[1] + # deal with divide by zero + coverage = 1.0 + if coverred + not_coverred != 0: + coverage = float(coverred) / (coverred + not_coverred) + modules[module][TREECOVERAGE] = (coverred, not_coverred, coverage) + return modules[module][TREECOVERAGE] + + for module in modules: + modules[module][SELFCOVERAGE] = coverage[module] + + for module in modules: + modules[module][TREECOVERAGE] = dfs(module) + return modules + +# arg1: tree coverage results +# arg2: coverage type +def sort_coverage(coverage, coverage_type): + l = [(module, coverage[module][coverage_type])for module in coverage] + l.sort(key=lambda x:x[1][2]) + return l + +def print_tree_coverage(tree_coverage): + def dfs(module, level): + # print current node + tree = tree_coverage[module][TREECOVERAGE] + self = tree_coverage[module][SELFCOVERAGE] + print(" " * level + "- " + module) + print(" " * level + " tree", end="") + print("(%d, %d, %.2f)" % (tree[0], tree[1], tree[2] * 100.0)) + print(" " * level + " self", end="") + print("(%d, %d, %.2f)" % (self[0], self[1], self[2] * 100.0)) + + # print children nodes + if CHILDREN in modules[module]: + # the dfs part + for child in modules[module][CHILDREN]: + dfs(child[MODULE], level + 1) + + for module in tree_coverage: + if tree_coverage[module][TYPE] == ROOT: + dfs(module, 0) if __name__ == "__main__": assert len(sys.argv) == 2, "Expect input_file" input_file = sys.argv[1] - coverred = 0 - not_coverred = 0 - with open(input_file) as f: - for line in f: - coverred_pattern = re.compile('^\s*(\d+)\s+if') - not_coverred_pattern = re.compile('^\s*(%0+)\s+if') + pp = pprint.PrettyPrinter(indent=4) + + lines = get_lines(input_file) + # print("lines:") + # pp.pprint(lines) + + annotations = get_line_annotation(lines) + # print("annotations:") + # pp.pprint(annotations) + + modules = get_modules(lines) + # print("modules:") + # pp.pprint(modules) + + self_coverage = {module: get_coverage_statistics(annotations, modules[module][BEGIN], modules[module][END]) + for module in modules} + # print("self_coverage:") + # pp.pprint(self_coverage) + + tree_coverage = get_tree_coverage(modules, self_coverage) + # print("tree_coverage:") + # pp.pprint(tree_coverage) - coverred_match = coverred_pattern.search(line) - not_coverred_match = not_coverred_pattern.search(line) + print("SelfCoverage:") + pp.pprint(sort_coverage(tree_coverage, SELFCOVERAGE)) - assert not (coverred_match and not_coverred_match) - - if coverred_match: - coverred += 1 + print("TreeCoverage:") + pp.pprint(sort_coverage(tree_coverage, TREECOVERAGE)) - if not_coverred_match: - not_coverred += 1 - print("cover: %d not_cover: %d coverage: %f" % - (coverred, not_coverred, float(coverred) / (coverred + not_coverred))) + print("AllCoverage:") + print_tree_coverage(tree_coverage) diff --git a/src/main/scala/utils/ParallelMux.scala b/src/main/scala/utils/ParallelMux.scala index 6aa6d953f40ad621c5d8378dc1280776cb22705a..636027b024dd1593d43fbd4e3f186ba502730f33 100644 --- a/src/main/scala/utils/ParallelMux.scala +++ b/src/main/scala/utils/ParallelMux.scala @@ -4,7 +4,7 @@ import chisel3._ import chisel3.util._ object ParallelOperation { - def apply[T <: Data](xs: Seq[T], func: (T, T) => T): T = { + def apply[T](xs: Seq[T], func: (T, T) => T): T = { require(xs.nonEmpty) xs match { case Seq(a) => a @@ -21,12 +21,22 @@ object ParallelOR { } } +object ParallelORR { + def apply(in: Seq[Bool]): Bool = ParallelOR(in) + def apply(in: Bits): Bool = apply(in.asBools) +} + object ParallelAND { def apply[T <: Data](xs: Seq[T]): T = { ParallelOperation(xs, (a: T, b:T) => (a.asUInt() & b.asUInt()).asTypeOf(xs.head)) } } +object ParallelANDR { + def apply(in: Seq[Bool]): Bool = ParallelAND(in) + def apply(in: Bits): Bool = apply(in.asBools) +} + object ParallelMux { def apply[T<:Data](in: Seq[(Bool, T)]): T = { val xs = in map { case (cond, x) => (Fill(x.getWidth, cond) & x.asUInt()).asTypeOf(in.head._2) } @@ -50,4 +60,17 @@ object ParallelMin { def apply[T <: Data](xs: Seq[T]): T = { ParallelOperation(xs, (a: T, b:T) => Mux(a.asUInt() < b.asUInt(),a, b).asTypeOf(xs.head)) } -} \ No newline at end of file +} + +object ParallelPriorityMux { + def apply[T <: Data](in: Seq[(Bool, T)]): T = { + ParallelOperation(in, (a: (Bool, T), b: (Bool, T)) => (a._1 || b._1, Mux(a._1, a._2, b._2)))._2 + } + def apply[T <: Data](sel: Bits, in: Seq[T]): T = apply((0 until in.size).map(sel(_)), in) + def apply[T <: Data](sel: Seq[Bool], in: Seq[T]): T = apply(sel zip in) +} + +object ParallelPriorityEncoder { + def apply(in: Seq[Bool]): UInt = ParallelPriorityMux(in, (0 until in.size).map(_.asUInt)) + def apply(in: Bits): UInt = apply(in.asBools) +} diff --git a/src/main/scala/utils/PriorityMuxGen.scala b/src/main/scala/utils/PriorityMuxGen.scala new file mode 100644 index 0000000000000000000000000000000000000000..e178b56006388bd948037c3317ac3c25609320a8 --- /dev/null +++ b/src/main/scala/utils/PriorityMuxGen.scala @@ -0,0 +1,19 @@ +package utils + +import chisel3._ +import chisel3.util._ + +// this could be used to handle the situation +// in which we have mux sources at multiple +// locations, and this is same to multiple +// when clauses as below, but collect them +// and put them into a ParallelPrioriyMux +// when (sel1) { x := in1 } +// when (sel2) { x := in2 } +class PriorityMuxGenerator[T <: Data] { + var src: List[(Bool, T)] = List() + def register(sel: Bool, in: T) = src = (sel, in) :: src + def register(in: Seq[(Bool, T)]) = src = in.toList ::: src + def register(sel: Seq[Bool], in: Seq[T]) = src = (sel zip in).toList ::: src + def apply(): T = ParallelPriorityMux(src) +} \ No newline at end of file diff --git a/src/main/scala/xiangshan/Bundle.scala b/src/main/scala/xiangshan/Bundle.scala index 4c0dd6027e25662fa826d2154031728750ea9086..eb460bf82f49d401133c967fb12c21e10162ed6d 100644 --- a/src/main/scala/xiangshan/Bundle.scala +++ b/src/main/scala/xiangshan/Bundle.scala @@ -90,7 +90,7 @@ class BranchPrediction extends XSBundle with HasIFUConst { def lastHalfRVIClearMask = ~lastHalfRVIMask // is taken from half RVI - def lastHalfRVITaken = (takens & lastHalfRVIMask).orR + def lastHalfRVITaken = ParallelORR(takens & lastHalfRVIMask) def lastHalfRVIIdx = Mux(firstBankHasHalfRVI, (bankWidth-1).U, (PredictWidth-1).U) // should not be used if not lastHalfRVITaken @@ -102,18 +102,18 @@ class BranchPrediction extends XSBundle with HasIFUConst { def brNotTakens = ~realTakens & realBrMask def sawNotTakenBr = VecInit((0 until PredictWidth).map(i => - (if (i == 0) false.B else brNotTakens(i-1,0).orR))) + (if (i == 0) false.B else ParallelORR(brNotTakens(i-1,0))))) // def hasNotTakenBrs = (brNotTakens & LowerMaskFromLowest(realTakens)).orR - def unmaskedJmpIdx = PriorityEncoder(takens) - def saveHalfRVI = (firstBankHasHalfRVI && (unmaskedJmpIdx === (bankWidth-1).U || !(takens.orR))) || + def unmaskedJmpIdx = ParallelPriorityEncoder(takens) + def saveHalfRVI = (firstBankHasHalfRVI && (unmaskedJmpIdx === (bankWidth-1).U || !(ParallelORR(takens)))) || (lastBankHasHalfRVI && unmaskedJmpIdx === (PredictWidth-1).U) // could get PredictWidth-1 when only the first bank is valid - def jmpIdx = PriorityEncoder(realTakens) + def jmpIdx = ParallelPriorityEncoder(realTakens) // only used when taken - def target = targets(jmpIdx) - def taken = realTakens.orR - def takenOnBr = taken && realBrMask(jmpIdx) - def hasNotTakenBrs = Mux(taken, sawNotTakenBr(jmpIdx), brNotTakens.orR) + def target = ParallelPriorityMux(realTakens, targets) + def taken = ParallelORR(realTakens) + def takenOnBr = taken && ParallelPriorityMux(realTakens, realBrMask.asBools) + def hasNotTakenBrs = Mux(taken, ParallelPriorityMux(realTakens, sawNotTakenBr), ParallelORR(brNotTakens)) } class BranchInfo extends XSBundle with HasBPUParameter { diff --git a/src/main/scala/xiangshan/frontend/IFU.scala b/src/main/scala/xiangshan/frontend/IFU.scala index 55347e7c66d22f8b38443bf674a3453d01c63475..eb48f3d2b37aadbcf38cabf3d5ab840b1bae4ad2 100644 --- a/src/main/scala/xiangshan/frontend/IFU.scala +++ b/src/main/scala/xiangshan/frontend/IFU.scala @@ -140,23 +140,15 @@ class IFU extends XSModule with HasIFUConst .elsewhen (if2_flush) { if2_valid := false.B } .elsewhen (if2_fire) { if2_valid := false.B } - when (RegNext(reset.asBool) && !reset.asBool) { - if1_npc := resetVector.U(VAddrBits.W) - }.elsewhen (if2_fire) { - if1_npc := if2_snpc - }.otherwise { - if1_npc := RegNext(if1_npc) - } - + val npcGen = new PriorityMuxGenerator[UInt] + npcGen.register(true.B, RegNext(if1_npc)) + npcGen.register(if2_fire, if2_snpc) val if2_bp = bpu.io.out(0) - - // val if2_GHInfo = wrapGHInfo(if2_bp, if2_predHist) + // if taken, bp_redirect should be true // when taken on half RVI, we suppress this redirect signal if2_redirect := if2_fire && if2_bp.taken - when (if2_redirect) { - if1_npc := if2_bp.target - } + npcGen.register(if2_redirect, if2_bp.target) if2_predicted_gh := if2_gh.update(if2_bp.hasNotTakenBrs, if2_bp.takenOnBr) @@ -166,7 +158,6 @@ class IFU extends XSModule with HasIFUConst val if3_fire = if3_valid && if4_ready && (inLoop || io.icacheResp.valid) && !if3_flush val if3_pc = RegEnable(if2_pc, if2_fire) val if3_predHist = RegEnable(if2_predHist, enable=if2_fire) - // val if3_nextValidPC = Mux(if2_valid) if3_ready := if3_fire || !if3_valid || if3_flush when (if3_flush) { if3_valid := false.B } .elsewhen (if2_fire) { if3_valid := true.B } @@ -181,7 +172,7 @@ class IFU extends XSModule with HasIFUConst val hasPrevHalfInstrReq = prevHalfInstrReq.valid val if3_prevHalfInstr = RegInit(0.U.asTypeOf(new PrevHalfInstr)) - // val if4_prevHalfInstr = Wire(new PrevHalfInstr) + // 32-bit instr crosses 2 pages, and the higher 16-bit triggers page fault val crossPageIPF = WireInit(false.B) @@ -238,10 +229,11 @@ class IFU extends XSModule with HasIFUConst // }.elsewhen (if3_ghInfoNotIdenticalRedirect) { // if3_target := Mux(if3_bp.taken, if3_bp.target, snpc(if3_pc)) // } + npcGen.register(if3_redirect, if3_target) - when (if3_redirect) { - if1_npc := if3_target - } + // when (if3_redirect) { + // if1_npc := if3_target + // } //********************** IF4 ****************************// val if4_pd = RegEnable(pd.io.out, if3_fire) @@ -350,9 +342,7 @@ class IFU extends XSModule with HasIFUConst // }.elsewhen (if4_ghInfoNotIdenticalRedirect) { // if4_target := Mux(if4_bp.taken, if4_bp.target, if4_snpc) // } - when (if4_redirect) { - if1_npc := if4_target - } + npcGen.register(if4_redirect, if4_target) when (if4_fire) { final_gh := if4_predicted_gh @@ -378,13 +368,11 @@ class IFU extends XSModule with HasIFUConst flush_final_gh := true.B } - when (loopBufPar.LBredirect.valid) { - if1_npc := loopBufPar.LBredirect.bits - } + npcGen.register(loopBufPar.LBredirect.valid, loopBufPar.LBredirect.bits) + npcGen.register(io.redirect.valid, io.redirect.bits) + npcGen.register(RegNext(reset.asBool) && !reset.asBool, resetVector.U(VAddrBits.W)) - when (io.redirect.valid) { - if1_npc := io.redirect.bits - } + if1_npc := npcGen() when(inLoop) { io.icacheReq.valid := if4_flush diff --git a/src/test/csrc/emu.cpp b/src/test/csrc/emu.cpp index 2da968bc18b97734c5b8f3f8c6de1b5cc632e379..c570f8b0bf647a42d2df5b6f207556442f2228d4 100644 --- a/src/test/csrc/emu.cpp +++ b/src/test/csrc/emu.cpp @@ -247,6 +247,13 @@ uint64_t Emulator::execute(uint64_t max_cycle, uint64_t max_instr) { diff.wdata = wdata; diff.wdst = wdst; +#if VM_COVERAGE == 1 + // we dump coverage into files at the end + // since we are not sure when an emu will stop + // we distinguish multiple dat files by emu start time + time_t start_time = time(NULL); +#endif + while (!Verilated::gotFinish() && trapCode == STATE_RUNNING) { if (!(max_cycle > 0 && max_instr > 0 && instr_left_last_cycle >= max_instr /* handle overflow */)) { trapCode = STATE_LIMIT_EXCEEDED; @@ -334,6 +341,11 @@ uint64_t Emulator::execute(uint64_t max_cycle, uint64_t max_instr) { #if VM_TRACE == 1 if (enable_waveform) tfp->close(); #endif + +#if VM_COVERAGE == 1 + save_coverage(start_time); +#endif + display_trapinfo(); return cycles; } @@ -363,6 +375,22 @@ inline char* Emulator::waveform_filename(time_t t) { return buf; } + +#if VM_COVERAGE == 1 +inline char* Emulator::coverage_filename(time_t t) { + static char buf[1024]; + char *p = timestamp_filename(t, buf); + strcpy(p, ".coverage.dat"); + return buf; +} + +inline void Emulator::save_coverage(time_t t) { + char *p = coverage_filename(t); + VerilatedCov::write(p); +} +#endif + + void Emulator::display_trapinfo() { uint64_t pc = dut_ptr->io_trap_pc; uint64_t instrCnt = dut_ptr->io_trap_instrCnt; diff --git a/src/test/csrc/emu.h b/src/test/csrc/emu.h index 4031a85ab1a58f84d76c093b2bbca9655f40f657..7caef9887e63f0521c780ebe8f7238e6d3e121d7 100644 --- a/src/test/csrc/emu.h +++ b/src/test/csrc/emu.h @@ -56,9 +56,13 @@ class Emulator { void display_trapinfo(); inline char* timestamp_filename(time_t t, char *buf); inline char* snapshot_filename(time_t t); + inline char* coverage_filename(time_t t); void snapshot_save(const char *filename); void snapshot_load(const char *filename); inline char* waveform_filename(time_t t); +#if VM_COVERAGE == 1 + inline void save_coverage(time_t t); +#endif public: Emulator(int argc, const char *argv[]);