Skip to content

Add unit tests for Branch module #1445

Merged
merged 15 commits into from
Dec 6, 2020
1 change: 1 addition & 0 deletions simulator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ set(TESTS_CPPS
func_sim/t/unit_test.cpp
modules/fetch/bpu/t/unit_test.cpp
modules/core/t/unit_test.cpp
modules/branch/t/unit_test.cpp
export/gdb/t/unit_test.cpp
export/cache/t/unit_test.cpp
)
Expand Down
3 changes: 2 additions & 1 deletion simulator/modules/branch/branch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ void Branch<FuncInstr>::clock( Cycle cycle)

#include <mips/mips.h>
#include <risc_v/risc_v.h>
#include <modules/t/test_instr.h>

template class Branch<BaseMIPSInstr<uint32>>;
template class Branch<BaseMIPSInstr<uint64>>;
template class Branch<RISCVInstr<uint32>>;
template class Branch<RISCVInstr<uint64>>;
template class Branch<RISCVInstr<uint128>>;

template class Branch<BranchTestInstr>;
2 changes: 1 addition & 1 deletion simulator/modules/branch/branch.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Branch : public Module
auto get_mispredictions_num() const { return num_mispredictions; }
auto get_jumps_num() const { return num_jumps; }

bool is_misprediction( const Instr& instr, const BPInterface& bp_data) const
static bool is_misprediction( const Instr& instr, const BPInterface& bp_data)
{
if ( !bp_data.is_hit)
if ( ( instr.is_common_branch() && instr.is_taken())
Expand Down
199 changes: 199 additions & 0 deletions simulator/modules/branch/t/unit_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/**
* Unit tests for Branch module
* @author Graudt Vladimir
* Copyright 2020 MIPT-MIPS
*/

#include <catch.hpp>
#include <modules/branch/branch.h>
#include <modules/t/test_instr.h>
#include <numeric>

namespace Test {

/* Emulates all modules, which can communicate with Branch */
class BranchTestingEnvironment : public Module {
public:
using Instr = PerfInstr<BranchTestInstr>;
using RegisterUInt = typename BranchTestInstr::RegisterUInt;
using InstructionOutput = std::array< RegisterUInt, MAX_DST_NUM>;

ReadPort<bool> *rp_flush = nullptr;
WritePort<bool> *wp_trap = nullptr;

ReadPort<Target> *rp_flush_target = nullptr;
ReadPort<BPInterface> *rp_bp_update = nullptr;

WritePort<Instr> *wp_datapath = nullptr;
ReadPort<Instr> *rp_datapath = nullptr;

ReadPort<InstructionOutput> *rp_bypass = nullptr;

ReadPort<bool> *rp_bypassing_unit_flush_notify = nullptr;

BranchTestingEnvironment( Module *parent);
};

/* Provides communication between Branch and Environment */
struct BranchTester : public Root {
Branch<BranchTestInstr> branch;
BranchTestingEnvironment env;

BranchTester() : Root( "branch_tester"), branch( this), env( this)
{ init_portmap(); }
};

BranchTestingEnvironment::BranchTestingEnvironment(Module *parent) :
Module( parent, "branch_testing_environment")
{
rp_flush = make_read_port<bool>( "BRANCH_2_ALL_FLUSH", Port::LATENCY);
wp_trap = make_write_port<bool>( "WRITEBACK_2_ALL_FLUSH", Port::BW);

rp_flush_target = make_read_port<Target>( "BRANCH_2_FETCH_TARGET", Port::LATENCY);
rp_bp_update = make_read_port<BPInterface>( "BRANCH_2_FETCH", Port::LATENCY);

wp_datapath = make_write_port<Instr>( "EXECUTE_2_BRANCH", Port::BW);
rp_datapath = make_read_port<Instr>( "BRANCH_2_WRITEBACK", Port::LATENCY);

rp_bypass = make_read_port<InstructionOutput>( "BRANCH_2_EXECUTE_BYPASS", Port::LATENCY);
rp_bypassing_unit_flush_notify = make_read_port<bool>( "BRANCH_2_BYPASSING_UNIT_FLUSH_NOTIFY", Port::LATENCY);
}

auto create_branch( Addr pc, Addr new_pc, Addr branch_target,
bool should_be_taken, bool predicted_as_taken)
{
BranchTestInstr func_instr( pc, (should_be_taken) ? branch_target : new_pc, branch_target, should_be_taken);
BPInterface bpi( pc, predicted_as_taken, (predicted_as_taken) ? branch_target : new_pc, true);

return PerfInstr<BranchTestInstr>( func_instr, bpi);
}


const Addr pc = 100; // PC of a branch
const Addr new_pc = pc + 4;
const Addr target = pc + 400; // target, if branch is taken

const auto cl_arrange = 0_cl; // setting environment
const auto cl_act = 1_cl; // running branch module
const auto cl_assert = 2_cl; // checking results

const auto taken_and_predicted_branch = create_branch(pc, new_pc, target, true, true);
const auto taken_and_npredicted_branch = create_branch(pc, new_pc, target, true, false);
const auto ntaken_and_predicted_branch = create_branch(pc, new_pc, target, false, true);
const auto ntaken_and_npredicted_branch = create_branch(pc, new_pc, target, false, false);

using InstrPtr = const PerfInstr<BranchTestInstr> *;

TEST_CASE( "Branch::is_misprediction()", "[branch_module]")
{
BranchTester t;
CHECK( !t.branch.is_misprediction( taken_and_predicted_branch, taken_and_predicted_branch.get_bp_data()));
CHECK( t.branch.is_misprediction( taken_and_npredicted_branch, taken_and_npredicted_branch.get_bp_data()));
CHECK( t.branch.is_misprediction( ntaken_and_predicted_branch, ntaken_and_predicted_branch.get_bp_data()));
CHECK( !t.branch.is_misprediction( ntaken_and_npredicted_branch, ntaken_and_npredicted_branch.get_bp_data()));
}

#define CHECK_PORT_READY( port) CHECK( ( port)->is_ready( cl_assert))
#define CHECK_PORT_NOT_READY( port) CHECK( !( port)->is_ready( cl_assert))
#define CHECK_PORT_READY_AND_TRUE( port) CHECK_PORT_READY( port); CHECK( ( port)->read( cl_assert) == true)
#define CHECK_PORT_NOT_READY_OR_FALSE( port) CHECK( (!(port)->is_ready( cl_assert) || ( port)->read( cl_assert) == false))

TEST_CASE( "correctly predicted branch", "[branch_module]")
{
BranchTester t;
InstrPtr instr = &taken_and_predicted_branch;

SECTION( "taken_and_predicted_branch") { instr = &taken_and_predicted_branch; }
SECTION( "ntaken_and_npredicted_branch") { instr = &ntaken_and_npredicted_branch; }

t.env.wp_datapath->write( *instr, cl_arrange);

t.branch.clock( cl_act);

CHECK_PORT_READY( t.env.rp_datapath);
CHECK_PORT_READY( t.env.rp_bp_update);
CHECK_PORT_READY( t.env.rp_bypass);
CHECK_PORT_NOT_READY( t.env.rp_flush_target);
CHECK_PORT_NOT_READY_OR_FALSE( t.env.rp_flush);
CHECK_PORT_NOT_READY_OR_FALSE( t.env.rp_bypassing_unit_flush_notify);
CHECK( t.branch.get_mispredictions_num() == 0);
CHECK( t.branch.get_jumps_num() == 0);
}

TEST_CASE( "mispredicted branch", "[branch_module]")
{
BranchTester t;
InstrPtr instr = nullptr;
Addr expected_target;

SECTION( "taken_and_npredicted_branch") { instr = &taken_and_npredicted_branch; expected_target = target; }
SECTION( "ntaken_and_predicted_branch") { instr = &ntaken_and_predicted_branch; expected_target = new_pc; }

t.env.wp_datapath->write( *instr, cl_arrange);

t.branch.clock( cl_act);

CHECK_PORT_READY( t.env.rp_datapath);
CHECK_PORT_READY( t.env.rp_bp_update);
CHECK_PORT_READY( t.env.rp_bypass);
CHECK_PORT_READY( t.env.rp_flush);
CHECK_PORT_READY( t.env.rp_flush_target);
CHECK_PORT_READY_AND_TRUE( t.env.rp_bypassing_unit_flush_notify);
CHECK( t.branch.get_mispredictions_num() == 1);
CHECK( t.branch.get_jumps_num() == 0);

auto actual_target = t.env.rp_flush_target->read( cl_assert);
CHECK( actual_target.valid);
CHECK( actual_target.address == expected_target);
CHECK( actual_target.sequence_id == instr->get_sequence_id() + 1);
}

TEST_CASE( "bypass check", "[branch_module]")
{
BranchTester t;
auto instr = taken_and_predicted_branch;
std::array<uint32, MAX_DST_NUM> dsts;

std::iota(dsts.begin(), dsts.end(), 228);
for ( int i = 0; i < MAX_DST_NUM; ++i)
instr.set_v_dst(dsts[i], i);
t.env.wp_datapath->write( instr, cl_arrange);

t.branch.clock( cl_act);

CHECK_PORT_READY( t.env.rp_bypass);
auto bypass_data = t.env.rp_bypass->read( cl_assert);
for ( int i = 0; i < MAX_DST_NUM; ++i) {
INFO( "i = " << i);
CHECK( bypass_data[i] == dsts[i]);
}
}

TEST_CASE( "trap received", "[branch_module]")
{
BranchTester t;
InstrPtr instr = nullptr;

SECTION( "taken_and_predicted_branch") { instr = &taken_and_predicted_branch; }
SECTION( "taken_and_npredicted_branch") { instr = &taken_and_npredicted_branch; }
SECTION( "ntaken_and_predicted_branch") { instr = &ntaken_and_predicted_branch; }
SECTION( "ntaken_and_npredicted_branch") { instr = &ntaken_and_npredicted_branch; }

t.env.wp_datapath->write( *instr, cl_arrange);
t.env.wp_trap->write(true, cl_arrange);

t.branch.clock( cl_act);

CHECK_PORT_NOT_READY( t.env.rp_datapath);
CHECK_PORT_NOT_READY( t.env.rp_bp_update);
CHECK_PORT_NOT_READY( t.env.rp_bypass);
}

TEST_CASE( "instruction dump", "[branch_module]")
{
std::ostringstream oss;
oss << BranchTestInstr( 0, 0, 0, false);
CHECK( oss.str() == "BranchTestInstr");
}

pavelkryukov marked this conversation as resolved.
Show resolved Hide resolved
} // end of Test namespace
23 changes: 23 additions & 0 deletions simulator/modules/t/test_instr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Simplified instructions for modules testing
* @author Graudt Vladimir
* Copyright 2020 MIPT-MIPS
*/

#include <func_sim/operation.h>
#include <infra/types.h>

class BranchTestInstr : public Datapath<uint32> {
public:
using RegisterUInt = uint32;

BranchTestInstr( Addr pc, Addr new_pc, Addr branch_target, bool is_taken) :
Datapath<uint32>(pc, new_pc)
{
target = branch_target;
is_taken_branch = is_taken;
}
};

inline std::ostream&
operator <<( std::ostream& os, const BranchTestInstr&) { return os << "BranchTestInstr"; }