Skip to content

Commit e370d4f

Browse files
jeffnye-ghjeff
andauthored
unit test template (#236)
A unit test bench template. It intends to help new users create unit tests. Three components, Src, Sink, Dut. Handles json and stf input test types. Conforms to coding style through clang-formt. I created this for myself a while back to help learning the concepts, updated for coding style. It was useful to me when learning sparta/map/olympia. --------- Co-authored-by: jeff <jeffnye-gh@github.com>
1 parent 8d8fa41 commit e370d4f

File tree

17 files changed

+1025
-0
lines changed

17 files changed

+1025
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,4 @@ add_custom_target(clangformat-check
127127
COMMENT
128128
"Formatting ${prefix} with ${CLANGFORMAT_EXECUTABLE} ..."
129129
)
130+

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ add_subdirectory(core/icache)
3636
add_subdirectory(core/branch_pred)
3737
add_subdirectory(core/dcache)
3838
add_subdirectory(core/vector)
39+
add_subdirectory(core/unit_template)
3940
add_subdirectory(fusion)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
set(PRJ "UNIT_TMPL")
2+
set(EXE "${PRJ}_tb")
3+
4+
# There are 3 tests
5+
# 1 - basic no options - sanity check
6+
# 2 - (z)stf input-file
7+
# 3 - JSON input-file
8+
set(TST1 "${PRJ}_test")
9+
set(TST2 "${PRJ}_stf_test")
10+
set(TST3 "${PRJ}_json_test")
11+
12+
# Test args
13+
set(STF ./traces/bmi_pmp.bare.stf)
14+
set(JSON ./json/raw_integer.json)
15+
16+
# Test cfg : there is a single config for the unit bench
17+
set(CFG config/config.yaml)
18+
19+
project(${PRJ})
20+
21+
add_executable(${EXE} Testbench.cpp Dut.cpp Src.cpp)
22+
target_include_directories(${EXE} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
23+
24+
target_link_libraries(${EXE} core common_test ${STF_LINK_LIBS} mavis SPARTA::sparta)
25+
26+
file(CREATE_LINK ${SIM_BASE}/mavis/json
27+
${CMAKE_CURRENT_BINARY_DIR}/mavis_isa_files SYMBOLIC)
28+
29+
file(CREATE_LINK ${SIM_BASE}/arches
30+
${CMAKE_CURRENT_BINARY_DIR}/arches SYMBOLIC)
31+
32+
file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/config
33+
${CMAKE_CURRENT_BINARY_DIR}/config SYMBOLIC)
34+
35+
file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/json
36+
${CMAKE_CURRENT_BINARY_DIR}/json SYMBOLIC)
37+
38+
file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/traces
39+
${CMAKE_CURRENT_BINARY_DIR}/traces SYMBOLIC)
40+
41+
file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/expected_output
42+
${CMAKE_CURRENT_BINARY_DIR}/expected_output SYMBOLIC)
43+
44+
sparta_named_test(${TST1} ${EXE} tb.out -c ${CFG})
45+
sparta_named_test(${TST2} ${EXE} tb_stf.out --input_file ${STF} -c ${CFG})
46+
sparta_named_test(${TST3} ${EXE} tb_json.out --input_file ${JSON} -c ${CFG})

test/core/unit_template/Dut.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#include "Dut.hpp"
2+
#include "sparta/utils/LogUtils.hpp"
3+
#include <algorithm>
4+
#include <iostream>
5+
using namespace std;
6+
7+
namespace olympia
8+
{
9+
constexpr char Dut::name[];
10+
11+
Dut::Dut(sparta::TreeNode* node, const DutParameterSet* p) :
12+
sparta::Unit(node),
13+
input_queue_("FetchQueue", p->input_queue_size, node->getClock(), &unit_stat_set_),
14+
num_to_process_(p->num_to_process)
15+
{
16+
input_queue_.enableCollection(node);
17+
18+
i_instgrp_write_.registerConsumerHandler(
19+
CREATE_SPARTA_HANDLER_WITH_DATA(Dut, inputQueueAppended_, InstGroupPtr));
20+
i_credits_.registerConsumerHandler(
21+
CREATE_SPARTA_HANDLER_WITH_DATA(Dut, receiveInpQueueCredits_, uint32_t));
22+
i_dut_flush_.registerConsumerHandler(
23+
CREATE_SPARTA_HANDLER_WITH_DATA(Dut, handleFlush_, FlushManager::FlushingCriteria));
24+
25+
sparta::StartupEvent(node, CREATE_SPARTA_HANDLER(Dut, sendInitialCredits_));
26+
}
27+
28+
// Send source the initial credit count
29+
void Dut::sendInitialCredits_() { o_restore_credits_.send(input_queue_.capacity()); }
30+
31+
// Receive Uop credits from Dispatch
32+
void Dut::receiveInpQueueCredits_(const uint32_t & credits)
33+
{
34+
inp_queue_credits_ += credits;
35+
if (input_queue_.size() > 0)
36+
{
37+
ev_process_insts_event_.schedule(sparta::Clock::Cycle(0));
38+
}
39+
40+
ILOG("Received credits: " << i_credits_);
41+
}
42+
43+
// Called when the input buffer was appended by source. If dut
44+
// has the credits, then schedule a processing session. Otherwise,
45+
// go to sleep
46+
void Dut::inputQueueAppended_(const InstGroupPtr & insts)
47+
{
48+
// Cache the instructions in the input queue if we can't
49+
// process them this cycle
50+
for (auto & i : *insts)
51+
{
52+
input_queue_.push(i);
53+
ILOG("Received: " << i);
54+
}
55+
if (inp_queue_credits_ > 0)
56+
{
57+
ev_process_insts_event_.schedule(sparta::Clock::Cycle(0));
58+
}
59+
}
60+
61+
// Handle incoming flush
62+
void Dut::handleFlush_(const FlushManager::FlushingCriteria & criteria)
63+
{
64+
ILOG("Got a flush call for " << criteria);
65+
o_restore_credits_.send(input_queue_.size());
66+
input_queue_.clear();
67+
}
68+
69+
void Dut::processInsts_()
70+
{
71+
uint32_t num_process = std::min(inp_queue_credits_, input_queue_.size());
72+
num_process = std::min(num_process, num_to_process_);
73+
74+
if (num_process > 0)
75+
{
76+
InstGroupPtr insts =
77+
sparta::allocate_sparta_shared_pointer<InstGroup>(instgroup_allocator);
78+
79+
// Send instructions on their way to sink unit
80+
for (uint32_t i = 0; i < num_process; ++i)
81+
{
82+
const auto & inst = input_queue_.read(0);
83+
insts->emplace_back(inst);
84+
// This is a placeholder, there isn't a more accurate state
85+
// defined in the existing Inst.h.
86+
inst->setStatus(Inst::Status::RENAMED);
87+
ILOG("Dut: " << inst);
88+
input_queue_.pop();
89+
}
90+
91+
// Send processed instructions to sink
92+
o_instgrp_write_.send(insts);
93+
94+
// Decrement internal Uop Queue credits
95+
inp_queue_credits_ -= num_process;
96+
97+
// Send credits back to Fetch to get more instructions
98+
o_restore_credits_.send(num_process);
99+
}
100+
101+
// If we still have credits to send instructions as well as
102+
// instructions in the queue, schedule another processing session
103+
if (inp_queue_credits_ > 0 && input_queue_.size() > 0)
104+
{
105+
ev_process_insts_event_.schedule(1);
106+
}
107+
}
108+
} // namespace olympia

test/core/unit_template/Dut.hpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#pragma once
2+
3+
#include "FlushManager.hpp"
4+
#include "InstGroup.hpp"
5+
6+
#include "sparta/ports/DataPort.hpp"
7+
#include "sparta/events/UniqueEvent.hpp"
8+
#include "sparta/simulation/Unit.hpp"
9+
#include "sparta/simulation/TreeNode.hpp"
10+
#include "sparta/simulation/ParameterSet.hpp"
11+
12+
#include <string>
13+
14+
namespace olympia
15+
{
16+
class Dut : public sparta::Unit
17+
{
18+
public:
19+
//! \brief Parameters for Dut model
20+
class DutParameterSet : public sparta::ParameterSet
21+
{
22+
public:
23+
DutParameterSet(sparta::TreeNode* n) : sparta::ParameterSet(n) {}
24+
25+
PARAMETER(uint32_t, num_to_process, 4, "Number of instructions to process")
26+
PARAMETER(uint32_t, input_queue_size, 10, "Size of the input queue")
27+
};
28+
29+
Dut(sparta::TreeNode* node, const DutParameterSet* p);
30+
31+
//! \brief Name of this resource. Required by sparta::UnitFactory
32+
static constexpr char name[] = "dut";
33+
34+
private:
35+
// written by src unit
36+
InstQueue input_queue_;
37+
38+
// Port listening to the in-queue appends - Note the 1 cycle
39+
// src to dut instrgrp
40+
sparta::DataInPort<InstGroupPtr> i_instgrp_write_{&unit_port_set_, "i_instgrp_write", 1};
41+
42+
// dut to src restore credits
43+
sparta::DataOutPort<uint32_t> o_restore_credits_{&unit_port_set_, "o_restore_credits"};
44+
45+
// dut to sink instgrp
46+
sparta::DataOutPort<InstGroupPtr> o_instgrp_write_{&unit_port_set_, "o_instgrp_write"};
47+
48+
// sink to dut restore credits
49+
sparta::DataInPort<uint32_t> i_credits_{&unit_port_set_, "i_credits",
50+
sparta::SchedulingPhase::Tick, 0};
51+
52+
// For flush
53+
sparta::DataInPort<FlushManager::FlushingCriteria> i_dut_flush_{
54+
&unit_port_set_, "i_dut_flush", sparta::SchedulingPhase::Flush, 1};
55+
56+
// The process instruction event
57+
sparta::UniqueEvent<> ev_process_insts_event_{&unit_event_set_, "process_insts_event",
58+
CREATE_SPARTA_HANDLER(Dut, processInsts_)};
59+
60+
// Dut callbacks
61+
void sendInitialCredits_();
62+
void inputQueueAppended_(const InstGroupPtr &);
63+
void receiveInpQueueCredits_(const uint32_t &);
64+
void processInsts_();
65+
void handleFlush_(const FlushManager::FlushingCriteria & criteria);
66+
67+
uint32_t inp_queue_credits_ = 0;
68+
const uint32_t num_to_process_;
69+
};
70+
} // namespace olympia

test/core/unit_template/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Unit testbench template
2+
3+
This is an example of a unit test bench that
4+
could be used as a starting point for new test benches.
5+
6+
The source here intends to meet the coding guidelines
7+
enforced with clang-format. There is a script to help
8+
with this but it does not overwrite files automatically.
9+
10+
There some extra documentation and some standardization
11+
in the port naming conventions.
12+
13+
The unit organization is as shown:
14+
15+
```
16+
i_dut_flush ---
17+
|
18+
|--------| |--------| |--------|
19+
| |---A->>| |---C->>| |
20+
| src | | dut | | sink |
21+
| |<<-B---| |<<-D---| |
22+
|--------| |--------| |--------|
23+
24+
src.ports.o_instgrp_write ----A->> dut.ports.i_instgrp_write
25+
src.ports.i_credits <<--B--- dut.ports.o_restore_credits
26+
27+
dut.ports.o_instgrp_write ----C->> sink.ports.i_instgrp_write
28+
dut.ports.i_credits <<--D--- sink.ports.o_restore_credits
29+
```

test/core/unit_template/Sink.hpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#pragma once
2+
3+
#include "sparta/simulation/ParameterSet.hpp"
4+
#include "sparta/simulation/ResourceFactory.hpp"
5+
#include "sparta/simulation/TreeNode.hpp"
6+
7+
#include <string>
8+
9+
namespace core_test
10+
{
11+
// ----------------------------------------------------------------------
12+
// "Sink" unit, just sinks instructions sent to it. Sends credits
13+
// back as directed by params/execution mode
14+
// ----------------------------------------------------------------------
15+
class Sink : public sparta::Unit
16+
{
17+
public:
18+
static constexpr char name[] = "sink";
19+
20+
class SinkParameters : public sparta::ParameterSet
21+
{
22+
public:
23+
explicit SinkParameters(sparta::TreeNode* n) : sparta::ParameterSet(n) {}
24+
25+
PARAMETER(uint32_t, sink_queue_size, 10, "Sink queue size for testing")
26+
27+
PARAMETER(std::string, purpose, "grp", "Purpose of this Sink: grp, single")
28+
};
29+
30+
Sink(sparta::TreeNode* n, const SinkParameters* params) :
31+
sparta::Unit(n),
32+
credits_(params->sink_queue_size),
33+
credits_to_send_back_(credits_)
34+
{
35+
if (params->purpose == "grp")
36+
{
37+
i_instgrp_write_.registerConsumerHandler(CREATE_SPARTA_HANDLER_WITH_DATA(
38+
Sink, sinkInst_<olympia::InstGroupPtr>, olympia::InstGroupPtr));
39+
}
40+
else if (params->purpose == "single")
41+
{
42+
i_instgrp_write_.registerConsumerHandler(CREATE_SPARTA_HANDLER_WITH_DATA(
43+
Sink, sinkInst_<olympia::InstPtr>, olympia::InstPtr));
44+
}
45+
else
46+
{
47+
throw std::invalid_argument("Usage: purpose must be one of 'grp' or 'single'");
48+
}
49+
sparta::StartupEvent(n, CREATE_SPARTA_HANDLER(Sink, sendCredits_));
50+
}
51+
52+
private:
53+
template <class InstType> void sinkInst_(const InstType & inst_or_insts)
54+
{
55+
if (credits_ > 0)
56+
--credits_;
57+
if constexpr (std::is_same_v<InstType, olympia::InstGroupPtr>)
58+
{
59+
for (auto ptr : *inst_or_insts)
60+
{
61+
ILOG("Instruction: '" << ptr << "' sinked");
62+
}
63+
}
64+
else
65+
{
66+
ILOG("Instruction: '" << inst_or_insts << "' sinked");
67+
}
68+
++credits_to_send_back_;
69+
ev_return_credits_.schedule(1);
70+
}
71+
72+
void sendCredits_()
73+
{
74+
o_restore_credits_.send(credits_to_send_back_);
75+
credits_ += credits_to_send_back_;
76+
credits_to_send_back_ = 0;
77+
}
78+
79+
sparta::DataOutPort<uint32_t> o_restore_credits_{&unit_port_set_, "o_restore_credits"};
80+
81+
sparta::DataInPort<olympia::InstGroupPtr> i_instgrp_write_{
82+
&unit_port_set_, "i_instgrp_write", sparta::SchedulingPhase::Tick, 1};
83+
84+
uint32_t credits_ = 0;
85+
sparta::UniqueEvent<> ev_return_credits_{&unit_event_set_, "return_credits",
86+
CREATE_SPARTA_HANDLER(Sink, sendCredits_)};
87+
uint32_t credits_to_send_back_ = 0;
88+
};
89+
90+
using SinkFactory = sparta::ResourceFactory<Sink, Sink::SinkParameters>;
91+
} // namespace core_test

0 commit comments

Comments
 (0)