blob: 62c150cfc0373706b52479f3a764e024fc3b2032 [file]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "riscv/riscv_plic.h"
#include <cstdint>
#include "absl/log/check.h"
#include "googlemock/include/gmock/gmock.h"
#include "mpact/sim/generic/data_buffer.h"
// This file contains unit tests for the RiscV PLIC model.
namespace {
using ::mpact::sim::generic::DataBuffer;
using ::mpact::sim::generic::DataBufferFactory;
using ::mpact::sim::riscv::RiscVPlic;
using ::mpact::sim::riscv::RiscVPlicIrqInterface;
constexpr int kNumSources = 32;
constexpr int kNumContexts = 3;
// Mock interrupt target for a context.
class MockRiscVInterruptTarget : public RiscVPlicIrqInterface {
public:
void SetIrq(bool irq_value) override { irq_value_ = irq_value; }
bool irq_value() const { return irq_value_; }
void set_irq_value(bool value) { irq_value_ = value; }
private:
bool irq_value_ = false;
};
// Test fixture.
class RiscVPlicTest : public ::testing::Test {
protected:
RiscVPlicTest() {
plic_ = new RiscVPlic(kNumSources, kNumContexts);
for (int i = 0; i < kNumContexts; ++i) {
target_[i] = new MockRiscVInterruptTarget();
plic_->SetContext(i, target_[i]);
}
db_ = db_factory_.Allocate<uint32_t>(1);
db_->set_latency(0);
}
~RiscVPlicTest() override {
delete plic_;
for (int i = 0; i < kNumContexts; ++i) {
delete target_[i];
}
db_->DecRef();
}
// Convenience methods to read/write the PLIC state using its memory
// interface.
bool GetEnable(int source, int context) {
uint32_t word = 0x2000 + (context * 0x80) + (source >> 5);
int bit = source & 0x1f;
plic()->Load(word, db_, nullptr, nullptr);
return (db_->Get<uint32_t>(0) & (1 << bit)) != 0;
}
void SetEnable(int source, int context, bool value) {
uint32_t word = 0x2000 + (context * 0x80) + (source >> 5);
int bit = source & 0x1f;
plic()->Load(word, db_, nullptr, nullptr);
auto span = db_->Get<uint32_t>();
uint32_t mask = ~(1 << bit);
uint32_t u_val = static_cast<uint32_t>(value);
span[0] = (span[0] & mask) | (u_val << bit);
plic()->Store(word, db_);
}
bool GetPending(int source) {
uint32_t word = 0x1000 + ((source >> 5) << 2);
int bit = source & 0x1f;
plic()->Load(word, db_, nullptr, nullptr);
return (db_->Get<uint32_t>(0) & (1 << bit)) != 0;
}
void SetPending(int source, bool value) {
int word = 0x1000 + ((source >> 5) << 2);
int bit = source & 0x1f;
plic()->Load(word, db_, nullptr, nullptr);
auto span = db_->Get<uint32_t>();
uint32_t mask = ~(1 << bit);
span[0] = (span[0] & mask) | (static_cast<uint32_t>(value) << bit);
plic()->Store(word, db_);
}
uint32_t GetPriority(int source) {
plic()->Load(source << 2, db_, nullptr, nullptr);
return db_->Get<uint32_t>(0);
}
void SetPriority(int source, uint32_t priority) {
db_->Set<uint32_t>(0, priority);
plic()->Store(source << 2, db_);
}
int GetPriorityThreshold(int context) {
plic()->Load(0x20'0000 + context * 0x1000, db_, nullptr, nullptr);
return db_->Get<uint32_t>(0);
}
void SetPriorityThreshold(int context, uint32_t threshold) {
db_->Set<uint32_t>(0, threshold);
plic()->Store(0x20'0000 + context * 0x1000, db_);
}
uint32_t GetInterruptClaim(int context) {
plic()->Load(0x20'0000 + context * 0x1000 + 4, db_, nullptr, nullptr);
return db_->Get<uint32_t>(0);
}
void SetInterruptClaim(int context, uint32_t claim) {
db_->Set<uint32_t>(0, claim);
plic()->Store(0x20'0000 + context * 0x1000 + 4, db_);
}
void SetDefaultConfig() {
// Set priorities to increasing values for all sources.
// Thresholds are set 8, 16 and 24 for the three contexts respectively.
// Source 30 is enabled for all contexts.
auto status = plic()->Configure(
"0=0;1=1;2=2;3=3;4=4;5=5;6=6;7=7;8=8;9=9;10=10;11=11;"
"12=12;13=13;14=14;15=15;16=16;17=17;18=18;19=19;20=20;"
"21=21;22=22;23=23;24=24;25=25;26=26;27=27;28=28;29=29;"
"30=30;31=31;",
"0=8,1,1,2,1,3,1,4,1,5,1,6,1,7,1,8,1,9,1,10,1,30,1;"
"1=16,11,1,12,1,13,1,14,1,15,1,16,1,17,1,18,1,19,1,20,1,30,1;"
"2=24,19,1,20,1,21,1,22,1,23,1,24,1,25,1,26,1,27,1,28,1,29,1,30,1,31,"
"1;");
CHECK_OK(status);
}
// Accessors.
RiscVPlic *plic() { return plic_; }
DataBufferFactory &db_factory() { return db_factory_; }
MockRiscVInterruptTarget *target(int i) { return target_[i]; }
private:
DataBuffer *db_;
DataBufferFactory db_factory_;
RiscVPlic *plic_ = nullptr;
MockRiscVInterruptTarget *target_[kNumContexts];
};
// Test that the initial state of the PLIC is as expected. Nothing enabled,
// priorities at zero, priority thresholds at zero, no pending interrupts,
// no claimed interrupts, no IRQ lines set.
TEST_F(RiscVPlicTest, InitialState) {
for (int s = 0; s < kNumSources; ++s) {
for (int c = 0; c < kNumContexts; ++c) {
EXPECT_FALSE(GetEnable(s, c));
EXPECT_FALSE(GetPending(s));
EXPECT_EQ(GetPriority(s), 0) << "Source " << s;
EXPECT_EQ(GetPriorityThreshold(c), 0);
EXPECT_EQ(GetInterruptClaim(c), 0);
EXPECT_FALSE(target(c)->irq_value());
}
}
}
// Verify that the PLIC is configured as expected with the default configuration
// strings.
TEST_F(RiscVPlicTest, DefaultConfiguration) {
SetDefaultConfig();
for (int s = 0; s < kNumSources; ++s) {
for (int c = 0; c < kNumContexts; ++c) {
switch (c) {
case 0:
EXPECT_EQ(GetEnable(s, c), ((s >= 1) && (s <= 10)) || (s == 30))
<< "Source " << s << " context " << c;
break;
case 1:
EXPECT_EQ(GetEnable(s, c), ((s >= 11) && (s <= 20)) || (s == 30))
<< "Source " << s << " context " << c;
break;
case 2:
EXPECT_EQ(GetEnable(s, c), (s >= 19) && (s <= 31))
<< "Source " << s << " context " << c;
break;
}
EXPECT_FALSE(GetPending(s));
EXPECT_EQ(GetPriority(s), s) << "Source " << s;
EXPECT_EQ(GetPriorityThreshold(c), (c + 1) * 8) << "Context " << c;
EXPECT_EQ(GetInterruptClaim(c), 0) << "Context " << c;
EXPECT_FALSE(target(c)->irq_value()) << "Context " << c;
}
}
}
// Test operation of level triggered interrupts.
TEST_F(RiscVPlicTest, LevelTriggeredInterrupt) {
SetDefaultConfig();
// Set irq for source 10 level sensitive.
plic()->SetInterrupt(10, /*value=*/true, /*is_level=*/true);
// Expect pending to be set.
EXPECT_TRUE(GetPending(10));
// Clear the irq.
plic()->SetInterrupt(10, /*value=*/false, /*is_level=*/true);
// Expect pending to still be set.
EXPECT_TRUE(GetPending(10));
// Expect irq to be set for context 0;
EXPECT_TRUE(target(0)->irq_value());
// Clear the irq.
target(0)->set_irq_value(false);
// Now raise the irq again.
plic()->SetInterrupt(10, /*value=*/true, /*is_level=*/true);
// Expect pending to be set.
EXPECT_TRUE(GetPending(10));
// Claim the interrupt.
uint32_t id = GetInterruptClaim(0);
EXPECT_EQ(id, 10);
// A second claim should return 0.
EXPECT_EQ(GetInterruptClaim(0), 0);
// Interrupt is no longer pending.
EXPECT_FALSE(GetPending(10));
// Complete the interrupt.
SetInterruptClaim(0, id);
// Expect pending to be set, as the level is still high.
EXPECT_TRUE(GetPending(10));
// Lower the irq.
plic()->SetInterrupt(10, /*value=*/false, /*is_level=*/true);
// Expect pending to still be set.
EXPECT_TRUE(GetPending(10));
}
// Test operation of edge triggered interrupts.
TEST_F(RiscVPlicTest, EdgeTriggeredInterrupt) {
SetDefaultConfig();
// Set irq for source 10 level sensitive.
plic()->SetInterrupt(10, /*value=*/true, /*is_level=*/false);
// Expect pending to be set.
EXPECT_TRUE(GetPending(10));
// Expect irq to be set for context 0;
EXPECT_TRUE(target(0)->irq_value());
// Claim the interrupt.
uint32_t id = GetInterruptClaim(0);
EXPECT_EQ(id, 10);
// A second claim should return 0.
EXPECT_EQ(GetInterruptClaim(0), 0);
// Interrupt is no longer pending.
EXPECT_FALSE(GetPending(10));
// Complete the interrupt.
SetInterruptClaim(0, id);
// Expect pending to be cleared.
EXPECT_FALSE(GetPending(10));
}
TEST_F(RiscVPlicTest, PriorityThreshold) {
SetDefaultConfig();
// Signal interrupts for sources 5-10.
for (int i = 5; i <= 10; ++i) {
plic()->SetInterrupt(i, /*value=*/true, /*is_level=*/false);
// Only sources 9 and 10 should trigger interrupts.
EXPECT_EQ(target(0)->irq_value(), i > 8);
}
// Now claim the interrupts, all should be claimed in order of priority,'
// even those below the threshold. The IRQ line should remain high until
// there are no more pending interrupts.
for (int i = 10; i >= 5; --i) {
EXPECT_TRUE(target(0)->irq_value());
EXPECT_TRUE(GetPending(i));
uint32_t id = GetInterruptClaim(0);
EXPECT_EQ(id, i);
SetInterruptClaim(0, id);
EXPECT_FALSE(GetPending(i));
// IRQ remains high until the last interrupt is completed.
EXPECT_EQ(target(0)->irq_value(), i != 5);
}
}
TEST_F(RiscVPlicTest, MultipleTargets) {
SetDefaultConfig();
// Signal interrupt for source 30.
plic()->SetInterrupt(30, /*value=*/true, /*is_level=*/false);
EXPECT_TRUE(GetPending(30));
// Expect irq to be set for all contexts.
for (int c = 0; c < kNumContexts; ++c) {
EXPECT_TRUE(target(c)->irq_value());
}
// Try claiming the interrupt for each context, only the first will succeed.
for (int c = 0; c < kNumContexts; ++c) {
uint32_t id = GetInterruptClaim(c);
// Expect pending to be cleared.
EXPECT_FALSE(GetPending(30));
if (c == 0) {
EXPECT_EQ(id, 30);
} else {
EXPECT_EQ(id, 0);
}
// The target should be cleared.
EXPECT_EQ(target(c)->irq_value(), 0);
}
// Complete the interrupt for context 0.
SetInterruptClaim(0, 30);
}
} // namespace