Files
gem5/src/arch/arm/matrix.test.cc
Sascha Bischoff 5c43523d53 arch-arm: Add matrix register support for SME
We add support for the matrix registers to the Arm architecture. This
will be used to implement support for Arm's Scalable Matrix Extension
(SME) in subsequent commits.

We add an implementation of a matrix register for the Arm
architecture. These are akin to 2D vector registers in the sense that
they can be dynamically viewed as a variety of element sizes. As
widening the element size would reduce the matrix size by a factor of
element size, we instead layer multiple tiles of wider elements onto
the underlying matrix storage in order to retain square matrices.

We separate the storage of the matrix from the different views one can
have. The potential views are:

* Tiles: View the matrix as one or more tiles using a specified
  element size. As the element size increases the number of indexable
  tiles increases. When using the smallest granularity element size
  (bytes) there is a single tile. As an example, using 32-bit elements
  yields 4 tiles. Tiles are interleaved onto the underlaying matrix
  modulo element size. A tile supports 2D indexing ([][]), with the
  first index specifying the row index, and the second the column
  (element index within the row).

* A Horizontal/Vertical slice (row or a column) of a tile: Take the
  aforementioned tile, and extract a specified row or column slice
  from it. A slice supports standard []-based indexing. A tile slice
  must use the same underlying element type as is used for the tile.

* A Horizontal/Vertical slice (row or column) of the underlying matrix
  storage: Treat the matrix register as an array of vectors (rows or
  columns, rows preferred due to them being indepependent of the
  element size being used).

On simulator start-up the matrix registers are initialised to a
maximum size. At run-time the used size can by dynamically
adjusted. However, please note that as the matrix register class
doesn't know if a smaller size is being used, the class itself doesn't
do any bounds checking itself. This is left to the user.

Jira Issue: https://gem5.atlassian.net/browse/GEM5-1289

Change-Id: I6a6a05154846e4802e9822bbbac00ab2c39538ed
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/64334
Reviewed-by: Giacomo Travaglini <giacomo.travaglini@arm.com>
Maintainer: Giacomo Travaglini <giacomo.travaglini@arm.com>
Tested-by: kokoro <noreply+kokoro@google.com>
2023-01-17 10:09:56 +00:00

454 lines
13 KiB
C++

/*
* Copyright (c) 2022 Arm Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <gtest/gtest.h>
#include "arch/arm/matrix.hh"
using namespace gem5;
TEST(Matrix, Size)
{
{
// Minimum size
MatStore<1, 1> mat;
ASSERT_EQ(1, mat.linearSize());
}
{
// Medium size
constexpr size_t x_size = MaxMatRegRowLenInBytes / 2;
constexpr size_t y_size = MaxMatRegRows / 2;
MatStore<x_size, y_size> mat;
ASSERT_EQ(x_size * y_size, mat.linearSize());
}
{
// Maximum size
MatStore<MaxMatRegRowLenInBytes, MaxMatRegRows> mat;
ASSERT_EQ(MaxMatRegRowLenInBytes * MaxMatRegRows, mat.linearSize());
}
}
TEST(Matrix, Zero)
{
constexpr size_t size = 16;
MatStore<size, size> mat;
auto tile = mat.asTile<uint8_t>(0);
// Initializing with non-zero value
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
tile[i][j] = 0xAA;
}
}
// zeroing the matrix
mat.zero();
// checking if every matrix element is set to zero
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
ASSERT_EQ(tile[i][j], 0);
}
}
}
TEST(Matrix, ZeroTiles)
{
constexpr size_t size = 16;
MatStore<size, size> mat;
auto byte_tile = mat.asTile<uint8_t>(0);
// Initializing the whole tile with non-zero value
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
byte_tile[i][j] = 0xAA;
}
}
// zeroing the half-word tile 0 of matrix
auto half_word_tile = mat.asTile<uint16_t>(0);
half_word_tile.zero();
// Check that every element of half-word tile 0 is zero
for (auto i = 0; i < size / 2; i++) {
for (auto j = 0; j < size / 2; j++) {
ASSERT_EQ(half_word_tile[i][j], 0);
}
}
// Check that every element of half-word tile 1 is 0xAAAA (note the
// double width of the element)
half_word_tile = mat.asTile<uint16_t>(1);
for (auto i = 0; i < size / 2; i++) {
for (auto j = 0; j < size / 2; j++) {
ASSERT_EQ(half_word_tile[i][j], 0xAAAA);
}
}
// Check if every matrix element on an even row is set to zero
for (auto i = 0; i < size; i += 2) {
for (auto j = 0; j < size; j++) {
ASSERT_EQ(byte_tile[i][j], 0);
}
}
// Check if every matrix element on an odd row is set to 0xAA
for (auto i = 1; i < size; i += 2) {
for (auto j = 0; j < size; j++) {
ASSERT_EQ(byte_tile[i][j], 0xAA);
}
}
}
TEST(Matrix, ZeroTileHSlice)
{
constexpr size_t size = 16;
MatStore<size, size> mat;
auto byte_tile = mat.asTile<uint8_t>(0);
// Initializing the whole tile with non-zero value
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
byte_tile[i][j] = 0xAA;
}
}
// zeroing the 0th row of half-word tile 0
auto half_word_tile = mat.asTile<uint16_t>(0);
auto row = half_word_tile.asHSlice(0);
row.zero();
// Check that every element of the row is zero
for (auto i = 0; i < size / 2; i++) {
ASSERT_EQ(row[i], 0);
}
// Check that every element of row 1 is 0xAAAA
row = half_word_tile.asHSlice(1);
for (auto i = 0; i < size / 2; i++) {
ASSERT_EQ(row[i], 0xAAAA);
}
// Check that row 0 of the byte tile is zero, and that all remaining
// rows are unaffected
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
if (i == 0) {
ASSERT_EQ(byte_tile[i][j], 0);
} else {
ASSERT_EQ(byte_tile[i][j], 0xAA);
}
}
}
}
TEST(Matrix, ZeroTileVSlice)
{
constexpr size_t size = 16;
MatStore<size, size> mat;
auto byte_tile = mat.asTile<uint8_t>(0);
// Initializing the whole tile with non-zero value
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
byte_tile[i][j] = 0xAA;
}
}
// zeroing the 0th column of half-word tile 0
auto half_word_tile = mat.asTile<uint16_t>(0);
auto col = half_word_tile.asVSlice(0);
col.zero();
// Check that every element of the column is zero
for (auto i = 0; i < size / 2; i++) {
ASSERT_EQ(col[i], 0);
}
// Check that every element of column 1 is 0xAAAA
col = half_word_tile.asVSlice(1);
for (auto i = 0; i < size / 2; i++) {
ASSERT_EQ(col[i], 0xAAAA);
}
// Check that elements 0 & 1 of the byte tile are zero for even rows,
// and that all remaining elements are unaffected
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
if (i % 2 == 0 && (j == 0 || j == 1)) {
ASSERT_EQ(byte_tile[i][j], 0);
} else {
ASSERT_EQ(byte_tile[i][j], 0xAA);
}
}
}
}
TEST(Matrix, ZeroHSlice)
{
constexpr size_t size = 16;
MatStore<size, size> mat;
auto byte_tile = mat.asTile<uint8_t>(0);
// Initializing the whole tile with non-zero value
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
byte_tile[i][j] = 0xAA;
}
}
// Now we get a row directly from the matrix (as words, because it
// should make no difference), zero it
auto row = mat.asHSlice<uint32_t>(4);
row.zero();
// Check that every element of the row is zero
for (auto i = 0; i < size / 4; i++) {
ASSERT_EQ(row[i], 0);
}
// Check that row 4 of the byte tile is zero, and that all remaining
// rows are unaffected
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
if (i == 4) {
ASSERT_EQ(byte_tile[i][j], 0);
} else {
ASSERT_EQ(byte_tile[i][j], 0xAA);
}
}
}
}
TEST(Matrix, ZeroVSlice)
{
constexpr size_t size = 16;
MatStore<size, size> mat;
auto byte_tile = mat.asTile<uint8_t>(0);
// Initializing the whole tile with non-zero value
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
byte_tile[i][j] = 0xAA;
}
}
// Now we get a column directly from the matrix, zero it
auto col = mat.asVSlice<uint8_t>(4);
col.zero();
// Check that every element of the column is zero
for (auto i = 0; i < size; i++) {
ASSERT_EQ(col[i], 0);
}
// Check that col 4 of the byte tile is zero, and that all remaining
// rows are unaffected
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
if (j == 4) {
ASSERT_EQ(byte_tile[i][j], 0);
} else {
ASSERT_EQ(byte_tile[i][j], 0xAA);
}
}
}
// Now we repeat with a wider element type too. Reinitializing the
// whole tile with non-zero value
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
byte_tile[i][j] = 0xAA;
}
}
// Now we get a word-wide column directly from the matrix, zero it
auto wide_col = mat.asVSlice<uint32_t>(1);
wide_col.zero();
// Check that every element of the column is zero
for (auto i = 0; i < size; i++) {
ASSERT_EQ(wide_col[i], 0);
}
// Check that cols 4-7 of the byte tile are zero, and that all
// remaining rows are unaffected
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
if (j >= 4 && j <= 7) {
ASSERT_EQ(byte_tile[i][j], 0);
} else {
ASSERT_EQ(byte_tile[i][j], 0xAA);
}
}
}
}
class TwoDifferentMatRegs : public testing::Test
{
protected:
static constexpr size_t size = 4;
MatStore<size, size> mat1;
MatStore<size, size> mat2;
void
SetUp() override
{
auto tile1 = mat1.asTile<uint8_t>(0);
auto tile2 = mat2.asTile<uint8_t>(0);
// Initializing with non-zero value for matrix 1
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
tile1[i][j] = 0xAA;
}
}
// Initializing with zero value for matrix 2
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
tile2[i][j] = 0x0;
}
}
}
};
// Testing operator=
TEST_F(TwoDifferentMatRegs, Assignment)
{
// Copying the matrix
mat2 = mat1;
auto tile2 = mat2.asTile<uint8_t>(0);
// Checking if matrix 2 elements are 0xAA
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
ASSERT_EQ(tile2[i][j], 0xAA);
}
}
}
// Testing operator==
TEST_F(TwoDifferentMatRegs, Equality)
{
// Equality check
ASSERT_TRUE(mat1 == mat1);
ASSERT_TRUE(mat2 == mat2);
ASSERT_FALSE(mat1 == mat2);
}
// Testing operator!=
TEST_F(TwoDifferentMatRegs, Inequality)
{
// Inequality check
ASSERT_FALSE(mat1 != mat1);
ASSERT_FALSE(mat2 != mat2);
ASSERT_TRUE(mat1 != mat2);
}
// Testing operator<<
TEST_F(TwoDifferentMatRegs, Printing)
{
{
std::ostringstream stream;
stream << mat1;
ASSERT_EQ(stream.str(), "[aaaaaaaa_aaaaaaaa_aaaaaaaa_aaaaaaaa]");
}
{
std::ostringstream stream;
stream << mat2;
ASSERT_EQ(stream.str(), "[00000000_00000000_00000000_00000000]");
}
}
// Testing ParseParam
TEST_F(TwoDifferentMatRegs, ParseParam)
{
ParseParam<decltype(mat1)> parser;
parser.parse("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", mat1);
parser.parse("cccccccccccccccccccccccccccccccc", mat2);
for (auto i = 0; i < size; i++) {
for (auto j = 0; j < size; j++) {
ASSERT_EQ(mat1.asTile<uint8_t>(0)[i][j], 0xbb);
ASSERT_EQ(mat2.asTile<uint8_t>(0)[i][j], 0xcc);
}
}
}
// Testing ParseParam Underflow
TEST_F(TwoDifferentMatRegs, ParseParamUnderflow)
{
ParseParam<decltype(mat1)> parser;
// We should trigger a fatal() here.
EXPECT_ANY_THROW(parser.parse("b", mat1));
}
// Testing ParseParam Overflow
TEST_F(TwoDifferentMatRegs, ParseParamOverflow)
{
ParseParam<decltype(mat1)> parser;
// We should trigger a fatal() here.
EXPECT_ANY_THROW(parser.parse("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", mat1));
}
// Testing ShowParam
TEST_F(TwoDifferentMatRegs, ShowParam)
{
ShowParam<decltype(mat1)> parser;
{
std::stringstream ss;
parser.show(ss, mat1);
ASSERT_EQ(ss.str(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
{
std::stringstream ss;
parser.show(ss, mat2);
ASSERT_EQ(ss.str(), "00000000000000000000000000000000");
}
}