| // SPDX-License-Identifier: Apache-2.0 | |
| // SPDX-FileCopyrightText: Copyright OpenBMC Authors | |
| #include "base.h" | |
| #include "fpga/fpga-run.h" | |
| #include "fpga/fpga-tables.h" | |
| #include <array> | |
| #include <cstring> | |
| #include <functional> | |
| #include <map> | |
| #include <string> | |
| #include <gtest/gtest.h> | |
| namespace | |
| { | |
| using Record = std::array<uint8_t, NSM_FPGA_RECORD_SIZE>; | |
| uint64_t getLe(const uint8_t *p, size_t bytes) | |
| { | |
| uint64_t value = 0; | |
| for (size_t i = 0; i < bytes; ++i) { | |
| value |= uint64_t{p[i]} << (8 * i); | |
| } | |
| return value; | |
| } | |
| void putLe(Record &record, size_t offset, uint64_t value, size_t bytes) | |
| { | |
| for (size_t i = 0; i < bytes; ++i) { | |
| record[offset + i] = static_cast<uint8_t>(value >> (8 * i)); | |
| } | |
| } | |
| // A scripted bank behind the P0 window, answering one MMIO step at a time | |
| struct FakeBank { | |
| const nsm_fpga_map *map = nsm_fpga_map_p0(); | |
| std::map<uint64_t, uint32_t> words{{0x05000400ULL, 1}}; | |
| int pendingCompletions = 0; | |
| int faultCompletions = 0; | |
| int foreignByteReplies = 0; | |
| bool nackBytes = false; | |
| uint8_t byteData = 0; | |
| uint8_t acquireResult = NSM_FPGA_CTRL_OK; | |
| std::function<void(uint64_t)> onRead; | |
| std::function<void(uint32_t, uint8_t &, bool &)> onByte; | |
| size_t exchanges = 0; | |
| size_t waits = 0; | |
| size_t controls = 0; | |
| size_t words_sent = 0; | |
| Record control{}; | |
| Record header{}; | |
| // the station address and data of the last word record, as the three | |
| // frame header and extension once carried them | |
| Record extension{}; | |
| Record authority() const | |
| { | |
| Record r{}; | |
| putLe(r, 0, NSM_FPGA_PROFILE_IBC2, 4); | |
| r[4] = 1; | |
| r[5] = NSM_FPGA_AUTH_INITIALIZED | NSM_FPGA_AUTH_AVAILABLE | | |
| NSM_FPGA_AUTH_RELEASED; | |
| r[6] = NSM_FPGA_OWNER_NONE; | |
| putLe(r, 8, 0x68e7a1c0, 8); | |
| putLe(r, 20, 3, 4); | |
| return r; | |
| } | |
| Record controlReply() const | |
| { | |
| Record r{}; | |
| std::memcpy(r.data(), control.data(), 4); | |
| r[4] = control[4]; | |
| r[5] = (control[4] == NSM_FPGA_CTRL_ACQUIRE || | |
| control[4] == NSM_FPGA_CTRL_RECOVER || | |
| control[4] == NSM_FPGA_CTRL_ACTIVATE) | |
| ? acquireResult | |
| : static_cast<uint8_t>(NSM_FPGA_CTRL_OK); | |
| r[6] = NSM_FPGA_OWNER_NSM; | |
| r[7] = 1; | |
| std::memcpy(r.data() + 8, control.data() + 8, 24); | |
| return r; | |
| } | |
| bool busByte(uint32_t argument, uint8_t &data) | |
| { | |
| data = byteData; | |
| bool nack = nackBytes; | |
| if (onByte) { | |
| onByte(argument, data, nack); | |
| } | |
| return !nack; | |
| } | |
| // A bounded transaction through the same scripted bus, byte by byte | |
| Record transaction(Record r, uint8_t opcode, uint32_t argument) | |
| { | |
| const uint8_t writes = (opcode >> 3) & 0x3; | |
| const uint8_t reads = opcode & 0x7; | |
| const uint32_t device = argument & 0x7f; | |
| uint32_t payload = 0; | |
| for (uint8_t i = 0; i < writes; ++i) { | |
| const uint32_t byte = | |
| (argument >> (8 * (i + 1))) & 0xff; | |
| const uint32_t flags = | |
| (i == 0 ? NSM_FPGA_I2C_START : 0) | | |
| (reads == 0 && i + 1 == writes ? NSM_FPGA_I2C_STOP | |
| : 0); | |
| uint8_t data = 0; | |
| if (!busByte(device | (byte << 8) | (flags << 16), | |
| data)) { | |
| r[4] = NSM_FPGA_RESULT_TARGET_NACK; | |
| r[5] = NSM_FPGA_FLAG_OCCUPIED | | |
| NSM_FPGA_FLAG_QUIESCENT | | |
| NSM_FPGA_FLAG_BUS_RELEASED; | |
| r[28] = 1; | |
| r[29] = i; | |
| return r; | |
| } | |
| } | |
| for (uint8_t i = 0; i < reads; ++i) { | |
| const bool last = i + 1 == reads; | |
| const uint32_t flags = | |
| NSM_FPGA_I2C_READ | | |
| (i == 0 ? NSM_FPGA_I2C_START : 0) | | |
| (last ? NSM_FPGA_I2C_STOP | NSM_FPGA_I2C_READ_NACK | |
| : 0); | |
| uint8_t data = 0; | |
| if (!busByte(device | (flags << 16), data)) { | |
| r[4] = NSM_FPGA_RESULT_TARGET_NACK; | |
| r[5] = NSM_FPGA_FLAG_OCCUPIED | | |
| NSM_FPGA_FLAG_QUIESCENT | | |
| NSM_FPGA_FLAG_BUS_RELEASED; | |
| r[28] = 1; | |
| r[29] = static_cast<uint8_t>(writes | (i << 4)); | |
| return r; | |
| } | |
| payload |= static_cast<uint32_t>(data) << (8 * i); | |
| } | |
| r[4] = NSM_FPGA_RESULT_SUCCESS; | |
| r[5] = NSM_FPGA_FLAG_OCCUPIED | NSM_FPGA_FLAG_QUIESCENT | | |
| NSM_FPGA_FLAG_BUS_RELEASED | | |
| (reads ? NSM_FPGA_FLAG_READ_VALID : 0); | |
| putLe(r, 28, payload, 4); | |
| return r; | |
| } | |
| Record completion() | |
| { | |
| Record r{}; | |
| std::memcpy(r.data(), header.data(), 4); | |
| r[6] = NSM_FPGA_OWNER_NSM; | |
| r[7] = header[4]; | |
| std::memcpy(r.data() + 8, header.data() + 8, 20); | |
| if (pendingCompletions > 0) { | |
| pendingCompletions--; | |
| r[4] = NSM_FPGA_RESULT_PENDING; | |
| r[5] = NSM_FPGA_FLAG_OCCUPIED | NSM_FPGA_FLAG_PENDING; | |
| return r; | |
| } | |
| if (faultCompletions > 0) { | |
| faultCompletions--; | |
| r[4] = NSM_FPGA_RESULT_FAULT; | |
| r[5] = NSM_FPGA_FLAG_OCCUPIED | | |
| NSM_FPGA_FLAG_QUIESCENT | NSM_FPGA_FLAG_FAULTED; | |
| r[29] = 0x2; | |
| return r; | |
| } | |
| if (header[4] == NSM_FPGA_OP_WRITE32) { | |
| r[4] = NSM_FPGA_RESULT_SUCCESS; | |
| r[5] = NSM_FPGA_FLAG_OCCUPIED | NSM_FPGA_FLAG_QUIESCENT; | |
| return r; | |
| } | |
| if (nsm_fpga_xfer_opcode_valid(header[4])) { | |
| return transaction(r, header[4], | |
| static_cast<uint32_t>( | |
| getLe(header.data() + 28, 4))); | |
| } | |
| if (header[4] == NSM_FPGA_OP_BYTE) { | |
| if (foreignByteReplies > 0) { | |
| // the engine still answers for the previous op | |
| foreignByteReplies--; | |
| putLe(r, 24, getLe(header.data() + 24, 4) + 1, | |
| 4); | |
| } | |
| const auto argument = | |
| static_cast<uint32_t>(getLe(header.data() + 28, 4)); | |
| const bool read = ((argument >> 16) & 1) != 0; | |
| const bool stop = ((argument >> 18) & 1) != 0; | |
| uint8_t data = byteData; | |
| bool nack = nackBytes; | |
| if (onByte) { | |
| onByte(argument, data, nack); | |
| } | |
| if (nack) { | |
| r[4] = NSM_FPGA_RESULT_TARGET_NACK; | |
| r[5] = NSM_FPGA_FLAG_OCCUPIED | | |
| NSM_FPGA_FLAG_QUIESCENT | | |
| NSM_FPGA_FLAG_BUS_RELEASED; | |
| return r; | |
| } | |
| r[4] = NSM_FPGA_RESULT_SUCCESS; | |
| r[5] = NSM_FPGA_FLAG_OCCUPIED | | |
| NSM_FPGA_FLAG_QUIESCENT | | |
| (read ? NSM_FPGA_FLAG_READ_VALID : 0) | | |
| (stop ? NSM_FPGA_FLAG_BUS_RELEASED | |
| : NSM_FPGA_FLAG_BUS_HELD); | |
| r[28] = read ? data : 0; | |
| return r; | |
| } | |
| r[4] = NSM_FPGA_RESULT_SUCCESS; | |
| r[5] = NSM_FPGA_FLAG_OCCUPIED | NSM_FPGA_FLAG_QUIESCENT | | |
| NSM_FPGA_FLAG_READ_VALID; | |
| const uint64_t address = getLe(extension.data() + 20, 8); | |
| const auto it = words.find(address); | |
| putLe(r, 28, it == words.end() ? 0 : it->second, 4); | |
| if (onRead && header[4] == NSM_FPGA_OP_READ32) { | |
| onRead(address); | |
| } | |
| return r; | |
| } | |
| // Answer one step; false when the address is not part of the window | |
| bool answer(const nsm_fpga_step &step, Record &reply) | |
| { | |
| exchanges++; | |
| const nsm_fpga_window &w = map->window; | |
| if (step.kind == NSM_FPGA_STEP_WRITE) { | |
| Record record{}; | |
| std::memcpy(record.data(), step.record, record.size()); | |
| if (step.address == w.control_write) { | |
| control = record; | |
| controls++; | |
| } else if (step.address == w.compact) { | |
| // unfold the word record into a header view | |
| // and an address and data view | |
| words_sent++; | |
| header = Record{}; | |
| extension = Record{}; | |
| putLe(header, 0, NSM_FPGA_PROFILE_IBC2, 4); | |
| header[4] = record[21]; | |
| header[5] = record[20]; | |
| header[6] = record[22]; | |
| std::memcpy(header.data() + 8, record.data(), | |
| 20); | |
| std::memcpy(extension.data(), record.data(), | |
| 20); | |
| putLe(extension, 20, | |
| getLe(record.data() + 24, 4), 8); | |
| putLe(extension, 28, | |
| getLe(record.data() + 28, 4), 4); | |
| if (record[21] == NSM_FPGA_OP_WRITE32) { | |
| words[getLe(extension.data() + 20, 8)] = | |
| static_cast<uint32_t>(getLe( | |
| extension.data() + 28, 4)); | |
| } | |
| } else if (step.address == w.data_write) { | |
| header = record; | |
| } else { | |
| return false; | |
| } | |
| return true; | |
| } | |
| if (step.address == w.status) { | |
| reply = authority(); | |
| } else if (step.address == w.control_read) { | |
| reply = controlReply(); | |
| } else if (step.address == w.completion_read) { | |
| reply = completion(); | |
| } else { | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Run the job to its end; lostAt (1-based exchange) loses that answer | |
| nsm_fpga_run_action drive(nsm_fpga_run &run, size_t lostAt = 0, | |
| size_t noSessionAt = 0) | |
| { | |
| for (int guard = 0; guard < 4000; ++guard) { | |
| const nsm_fpga_step *step = nullptr; | |
| const auto action = nsm_fpga_run_next(&run, &step); | |
| if (action == NSM_FPGA_RUN_WAIT) { | |
| waits++; | |
| continue; | |
| } | |
| if (action != NSM_FPGA_RUN_SEND) { | |
| return action; | |
| } | |
| Record reply{}; | |
| if (!answer(*step, reply)) { | |
| ADD_FAILURE() << "step outside the window"; | |
| return NSM_FPGA_RUN_FAILED; | |
| } | |
| const bool lost = exchanges == lostAt; | |
| const bool noSession = exchanges == noSessionAt; | |
| nsm_fpga_run_reply(&run, | |
| step->kind == NSM_FPGA_STEP_READ | |
| ? reply.data() | |
| : nullptr, | |
| lost, noSession); | |
| } | |
| ADD_FAILURE() << "job did not end"; | |
| return NSM_FPGA_RUN_FAILED; | |
| } | |
| }; | |
| struct Runner { | |
| FakeBank bank; | |
| nsm_fpga_session session{}; | |
| nsm_fpga_run run{}; | |
| Runner() | |
| { | |
| session.grant = {0x68e7a1c0, 7, 4}; | |
| nsm_fpga_run_init(&run, bank.map, &session, nullptr); | |
| nsm_fpga_run_begin_round(&run); | |
| } | |
| nsm_fpga_run_action rows(const nsm_fpga_command *list, size_t count) | |
| { | |
| nsm_fpga_run_rows(&run, list, count); | |
| return bank.drive(run); | |
| } | |
| }; | |
| const nsm_fpga_command readCalibration[] = {{.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .capture = NSM_FPGA_BUF_ITEM, | |
| .u = {.csr = {0x05000400ULL, 0}}}}; | |
| } // namespace | |
| TEST(fpgaRun, wordReadIsTwoFramesAndCapturesTheWord) | |
| { | |
| Runner r; | |
| r.bank.words[0x05000400ULL] = 0x1234; | |
| EXPECT_EQ(r.rows(readCalibration, 1), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 2u); | |
| EXPECT_EQ(r.run.last_word, 0x1234u); | |
| ASSERT_EQ(r.run.item_len, 4u); | |
| EXPECT_EQ(r.run.item[0], 0x34); | |
| EXPECT_EQ(r.run.item[1], 0x12); | |
| EXPECT_TRUE(r.run.any_read); | |
| EXPECT_FALSE(r.run.wrote_any); | |
| EXPECT_EQ(r.session.data_sequence, 1u); | |
| // the header carried the sequence and the station | |
| EXPECT_EQ(r.bank.header[24], 1); | |
| EXPECT_EQ(r.bank.header[5], 29); | |
| } | |
| TEST(fpgaRun, pollWaitsBetweenReadsUntilTheBitIsSet) | |
| { | |
| Runner r; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .poll = NSM_FPGA_POLL_SET, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}}}; | |
| r.bank.words[0x0500045cULL] = 0; | |
| // the bit comes up after the second read, so the third read sees it | |
| int reads = 0; | |
| r.bank.onRead = [&](uint64_t) { | |
| if (++reads == 2) { | |
| r.bank.words[0x0500045cULL] = 0x1; | |
| } | |
| }; | |
| EXPECT_EQ(r.rows(rows, 1), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.waits, 2u); | |
| EXPECT_EQ(r.bank.exchanges, 6u); | |
| EXPECT_EQ(r.run.last_polled, 0x1u); | |
| EXPECT_EQ(r.run.wait_us, 5000u); | |
| EXPECT_EQ(r.session.data_sequence, 3u); | |
| } | |
| TEST(fpgaRun, pollBoundFailsTheItemWithoutFencing) | |
| { | |
| Runner r; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .poll = NSM_FPGA_POLL_SET, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}, | |
| .period_us = 100, | |
| .limit = 3}}; | |
| r.bank.words[0x0500045cULL] = 0; | |
| EXPECT_EQ(r.rows(rows, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_EQ(r.bank.exchanges, 6u); | |
| EXPECT_EQ(r.bank.waits, 2u); | |
| EXPECT_EQ(r.run.wait_us, 100u); | |
| EXPECT_FALSE(r.run.fence); | |
| EXPECT_NE(std::strstr(r.run.error, "poll condition"), nullptr); | |
| } | |
| TEST(fpgaRun, pendingCompletionIsReadAgainWithinTheBound) | |
| { | |
| Runner r; | |
| r.bank.pendingCompletions = 2; | |
| EXPECT_EQ(r.rows(readCalibration, 1), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 4u); | |
| Runner stuck; | |
| stuck.bank.pendingCompletions = 100; | |
| EXPECT_EQ(stuck.rows(readCalibration, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_EQ(stuck.bank.exchanges, 2u + 16u); | |
| EXPECT_FALSE(stuck.run.fence); | |
| EXPECT_NE(std::strstr(stuck.run.error, "pending"), nullptr); | |
| } | |
| TEST(fpgaRun, expectFailureFailsTheItemOrEndsTheGroup) | |
| { | |
| Runner r; | |
| static const nsm_fpga_command item[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .u = {.csr = {0x05000400ULL, 0}}, | |
| .expect = {.mask = 0x7, .value = 0}}}; | |
| EXPECT_EQ(r.rows(item, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_FALSE(r.run.end_group); | |
| EXPECT_FALSE(r.run.fence); | |
| EXPECT_NE(std::strstr(r.run.error, "expected state"), nullptr); | |
| static const nsm_fpga_command group[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .u = {.csr = {0x05000400ULL, 0}}, | |
| .expect = {.mask = 0x7, .value = 0}, | |
| .on_expect_fail = NSM_FPGA_END_GROUP, | |
| .group_state = NSM_FPGA_STATE_ABSENT, | |
| .group_health = NSM_FPGA_HEALTH_OK}}; | |
| Runner g; | |
| EXPECT_EQ(g.rows(group, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(g.run.end_group); | |
| EXPECT_EQ(g.run.group_state, NSM_FPGA_STATE_ABSENT); | |
| EXPECT_EQ(g.run.group_health, NSM_FPGA_HEALTH_OK); | |
| EXPECT_FALSE(g.run.fence); | |
| } | |
| TEST(fpgaRun, checkRowsReadTheGroupBufferWithoutFrames) | |
| { | |
| Runner r; | |
| r.run.group[0] = 0x12; | |
| r.run.group[1] = 0x02; | |
| r.run.group_len = 2; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_CHECK, | |
| .station = 1, | |
| .expect = {.buffer = NSM_FPGA_BUF_GROUP, | |
| .width = 1, | |
| .offset = 1, | |
| .mask = 0x10, | |
| .value = 0}, | |
| .on_expect_fail = NSM_FPGA_END_GROUP, | |
| .group_state = NSM_FPGA_STATE_ABSENT}, | |
| {.op = NSM_FPGA_OP_CHECK, | |
| .station = 1, | |
| .expect = {.buffer = NSM_FPGA_BUF_GROUP, | |
| .width = 1, | |
| .offset = 1, | |
| .mask = 0x02, | |
| .value = 0x02}, | |
| .on_expect_fail = NSM_FPGA_END_GROUP, | |
| .group_state = NSM_FPGA_STATE_UNAVAILABLE_OFFLINE, | |
| .group_health = NSM_FPGA_HEALTH_WARNING}}; | |
| EXPECT_EQ(r.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 0u); | |
| r.run.group[1] = 0x10; | |
| EXPECT_EQ(r.rows(rows, 2), NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(r.run.end_group); | |
| EXPECT_EQ(r.run.group_state, NSM_FPGA_STATE_ABSENT); | |
| EXPECT_EQ(r.bank.exchanges, 0u); | |
| } | |
| TEST(fpgaRun, runIfSkipsRowsWhoseConditionFails) | |
| { | |
| Runner r; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .capture = NSM_FPGA_BUF_ITEM, | |
| .u = {.csr = {0x05000400ULL, 0}}}, | |
| {.op = NSM_FPGA_OP_WRITE32, | |
| .station = 29, | |
| .u = {.csr = {0x05000438ULL, 0x55}}, | |
| .run_if = {.buffer = NSM_FPGA_BUF_ITEM, | |
| .width = 1, | |
| .mask = 0xff, | |
| .value = 0}}}; | |
| r.bank.words[0x05000400ULL] = 1; | |
| EXPECT_EQ(r.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.words.count(0x05000438ULL), 0u); | |
| EXPECT_FALSE(r.run.wrote_any); | |
| Runner w; | |
| w.bank.words[0x05000400ULL] = 0; | |
| EXPECT_EQ(w.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(w.bank.words[0x05000438ULL], 0x55u); | |
| EXPECT_TRUE(w.run.wrote_any); | |
| } | |
| TEST(fpgaRun, wordsAreOneRecordThenTheResult) | |
| { | |
| Runner r; | |
| r.bank.words[0x05000400ULL] = 0x1234; | |
| EXPECT_EQ(r.rows(readCalibration, 1), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 2u); | |
| EXPECT_EQ(r.bank.words_sent, 1u); | |
| EXPECT_EQ(r.run.last_word, 0x1234u); | |
| ASSERT_EQ(r.run.item_len, 4u); | |
| EXPECT_EQ(r.run.item[0], 0x34); | |
| EXPECT_EQ(r.session.data_sequence, 1u); | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .poll = NSM_FPGA_POLL_SET, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}}, | |
| {.op = NSM_FPGA_OP_ACK_CLEAR, | |
| .station = 29, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}}}; | |
| Runner w; | |
| w.bank.words[0x0500045cULL] = 0x75; | |
| EXPECT_EQ(w.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(w.bank.words[0x0500045cULL], 0x74u); | |
| EXPECT_EQ(w.bank.exchanges, 4u); | |
| EXPECT_EQ(w.bank.words_sent, 2u); | |
| // a pending completion is read again, a lost answer still fences | |
| Runner p; | |
| p.bank.pendingCompletions = 2; | |
| EXPECT_EQ(p.rows(readCalibration, 1), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(p.bank.exchanges, 4u); | |
| Runner l; | |
| nsm_fpga_run_rows(&l.run, readCalibration, 1); | |
| EXPECT_EQ(l.bank.drive(l.run, 2), NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(l.run.fence); | |
| } | |
| TEST(fpgaRun, ackClearWritesThePolledWordWithTheBitClear) | |
| { | |
| Runner r; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .poll = NSM_FPGA_POLL_SET, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}}, | |
| {.op = NSM_FPGA_OP_ACK_CLEAR, | |
| .station = 29, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}}}; | |
| r.bank.words[0x0500045cULL] = 0x75; | |
| EXPECT_EQ(r.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.words[0x0500045cULL], 0x74u); | |
| EXPECT_TRUE(r.run.wrote_any); | |
| EXPECT_EQ(r.bank.exchanges, 4u); | |
| } | |
| TEST(fpgaRun, rememberedReadFeedsAckClearGatedByTheWord) | |
| { | |
| // A stale response is acknowledged, an idle status is left alone | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .remember = 1, | |
| .u = {.csr = {0x0500045cULL, 0}}}, | |
| {.op = NSM_FPGA_OP_ACK_CLEAR, | |
| .station = 29, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}, | |
| .run_if = { | |
| .buffer = NSM_FPGA_BUF_WORD, .mask = 0x1, .value = 0x1}}}; | |
| Runner stale; | |
| stale.bank.words[0x0500045cULL] = 0x75; | |
| EXPECT_EQ(stale.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(stale.bank.words[0x0500045cULL], 0x74u); | |
| EXPECT_TRUE(stale.run.wrote_any); | |
| EXPECT_EQ(stale.bank.exchanges, 4u); | |
| Runner idle; | |
| idle.bank.words[0x0500045cULL] = 0x74; | |
| EXPECT_EQ(idle.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(idle.bank.words[0x0500045cULL], 0x74u); | |
| EXPECT_FALSE(idle.run.wrote_any); | |
| EXPECT_EQ(idle.bank.exchanges, 2u); | |
| } | |
| TEST(fpgaRun, cleanStationSkipsItsGuardRowsUntilSomethingTouchesIt) | |
| { | |
| // Guard rows clear a stale response; the closing ack leaves the station | |
| // clean, so the next item on it skips the guard until a failure, an | |
| // ACQUIRE or a RELEASE makes the station's state unknown again | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .remember = 1, | |
| .clean = NSM_FPGA_UNLESS_CLEAN, | |
| .u = {.csr = {0x0500045cULL, 0}}}, | |
| {.op = NSM_FPGA_OP_ACK_CLEAR, | |
| .station = 29, | |
| .clean = NSM_FPGA_UNLESS_CLEAN, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}, | |
| .run_if = {.buffer = NSM_FPGA_BUF_WORD, | |
| .mask = 0x1, | |
| .value = 0x1}}, | |
| {.op = NSM_FPGA_OP_WRITE32, | |
| .station = 29, | |
| .u = {.csr = {0x0500045cULL, 0x1}}}, | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .remember = 1, | |
| .u = {.csr = {0x0500045cULL, 0}}}, | |
| {.op = NSM_FPGA_OP_ACK_CLEAR, | |
| .station = 29, | |
| .clean = NSM_FPGA_LEAVES_CLEAN, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}}}; | |
| static const nsm_fpga_command failing[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .poll = NSM_FPGA_POLL_SET, | |
| .poll_mask = 0x2, | |
| .u = {.csr = {0x0500045cULL, 0}}, | |
| .limit = 2}}; | |
| Runner r; | |
| // The round runs inside a held session | |
| nsm_fpga_run_control(&r.run, NSM_FPGA_CTRL_ACQUIRE, 0); | |
| EXPECT_EQ(r.bank.drive(r.run), NSM_FPGA_RUN_DONE); | |
| r.bank.words[0x0500045cULL] = 0x1; | |
| auto item = [&](const nsm_fpga_command *list, size_t count) { | |
| const size_t before = r.bank.exchanges; | |
| nsm_fpga_run_begin_item(&r.run); | |
| const auto action = r.rows(list, count); | |
| return std::make_pair(action, r.bank.exchanges - before); | |
| }; | |
| // A stale response: both guard rows run, five word ops | |
| EXPECT_EQ(item(rows, 5), std::make_pair(NSM_FPGA_RUN_DONE, size_t{10})); | |
| EXPECT_EQ(r.bank.words[0x0500045cULL], 0u); | |
| // Left clean: the guard rows are skipped | |
| EXPECT_EQ(item(rows, 5), std::make_pair(NSM_FPGA_RUN_DONE, size_t{6})); | |
| // A RENEW keeps the session, so the station stays clean | |
| nsm_fpga_run_control(&r.run, NSM_FPGA_CTRL_RENEW, 0); | |
| EXPECT_EQ(r.bank.drive(r.run), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(item(rows, 5), std::make_pair(NSM_FPGA_RUN_DONE, size_t{6})); | |
| // A failed item on the station makes its state unknown: the guard | |
| // reads again and finds nothing stale to acknowledge | |
| EXPECT_EQ(item(failing, 1).first, NSM_FPGA_RUN_FAILED); | |
| EXPECT_EQ(item(rows, 5), std::make_pair(NSM_FPGA_RUN_DONE, size_t{8})); | |
| // So does a new session, whose control sequence starts afresh | |
| r.session.control_sequence = 0; | |
| nsm_fpga_run_control(&r.run, NSM_FPGA_CTRL_ACQUIRE, 0); | |
| EXPECT_EQ(r.bank.drive(r.run), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(item(rows, 5), std::make_pair(NSM_FPGA_RUN_DONE, size_t{8})); | |
| } | |
| TEST(fpgaRun, popDrainsUntilTheStatusShowsNoCredit) | |
| { | |
| Runner r; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_POP, | |
| .station = 37, | |
| .poll_mask = 0xfffffffc, | |
| .u = {.pop = {0x18ULL, 0x14ULL}}, | |
| .limit = 8}}; | |
| r.bank.words[0x18ULL] = 3 << 2; | |
| size_t dataReads = 0; | |
| r.bank.onRead = [&](uint64_t address) { | |
| if (address == 0x14ULL) { | |
| dataReads++; | |
| r.bank.words[0x18ULL] -= 1 << 2; | |
| } | |
| }; | |
| EXPECT_EQ(r.rows(rows, 1), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(dataReads, 3u); | |
| // four status reads and three data reads, two frames each | |
| EXPECT_EQ(r.bank.exchanges, 14u); | |
| Runner stuck; | |
| stuck.bank.words[0x18ULL] = 1 << 2; | |
| EXPECT_EQ(stuck.rows(rows, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_NE(std::strstr(stuck.run.error, "not drained"), nullptr); | |
| } | |
| TEST(fpgaRun, writeCapturedTakesTheGroupBytes) | |
| { | |
| Runner r; | |
| r.run.group[0] = 0x08; | |
| r.run.group[1] = 0x44; | |
| r.run.group[2] = 0x33; | |
| r.run.group[3] = 0x22; | |
| r.run.group[4] = 0x11; | |
| r.run.group_len = 5; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_WRITE_CAPTURED, | |
| .station = 1, | |
| .u = {.captured = {.dev_addr = 0x71, | |
| .flags = NSM_FPGA_I2C_START | NSM_FPGA_I2C_STOP, | |
| .offset = 0}}}, | |
| {.op = NSM_FPGA_OP_WRITE_CAPTURED, | |
| .station = 29, | |
| .u = {.captured = {.address = 0x05000438ULL, .offset = 1}}}}; | |
| uint32_t argument = 0; | |
| r.bank.onByte = [&](uint32_t arg, uint8_t &, bool &) { | |
| argument = arg; | |
| }; | |
| EXPECT_EQ(r.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| // the byte op carried the mux address, the captured mask and the flags | |
| EXPECT_EQ(argument, 0x71u | (0x08u << 8) | (0x6u << 16)); | |
| EXPECT_EQ(r.bank.words[0x05000438ULL], 0x11223344u); | |
| EXPECT_TRUE(r.run.wrote_any); | |
| // a buffer that does not reach the offset fails before the wire | |
| Runner s; | |
| EXPECT_EQ(s.rows(rows, 2), NSM_FPGA_RUN_FAILED); | |
| EXPECT_EQ(s.bank.exchanges, 0u); | |
| } | |
| TEST(fpgaRun, transactionWritesReadsCapturesAndNackFailsWithoutFence) | |
| { | |
| // one row: pointer byte 0x0e then two bytes read, captured | |
| Runner r; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_I2C_XFER, | |
| .station = 1, | |
| .capture = NSM_FPGA_BUF_ITEM, | |
| .u = {.xfer = {0x50, {0x0e, 0, 0}, 1, 2, 0, 0}}}}; | |
| std::vector<uint32_t> seen; | |
| r.bank.onByte = [&](uint32_t argument, uint8_t &data, bool &) { | |
| seen.push_back(argument); | |
| data = static_cast<uint8_t>(0x10 + seen.size()); | |
| }; | |
| EXPECT_EQ(r.rows(rows, 1), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 2u); | |
| ASSERT_EQ(r.run.item_len, 2u); | |
| EXPECT_EQ(r.run.item[0], 0x12); | |
| EXPECT_EQ(r.run.item[1], 0x13); | |
| EXPECT_TRUE(r.run.wrote_any); | |
| // the bus saw start+write, repeated start read, read with nack+stop | |
| ASSERT_EQ(seen.size(), 3u); | |
| EXPECT_EQ(seen[0], 0x50u | (0x0eu << 8) | (NSM_FPGA_I2C_START << 16)); | |
| EXPECT_EQ(seen[1], | |
| 0x50u | ((NSM_FPGA_I2C_READ | NSM_FPGA_I2C_START) << 16)); | |
| EXPECT_EQ(seen[2], 0x50u | ((NSM_FPGA_I2C_READ | NSM_FPGA_I2C_STOP | | |
| NSM_FPGA_I2C_READ_NACK) | |
| << 16)); | |
| // a NACK ends the item without a fence; the controller released the bus | |
| Runner n; | |
| n.bank.nackBytes = true; | |
| EXPECT_EQ(n.rows(rows, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_FALSE(n.run.fence); | |
| EXPECT_NE(std::strstr(n.run.error, "nack"), nullptr); | |
| // a captured write byte comes from the group buffer | |
| Runner c; | |
| static const nsm_fpga_command restore[] = { | |
| {.op = NSM_FPGA_OP_I2C_XFER, | |
| .station = 1, | |
| .u = {.xfer = {0x50, {0x7e, 0, 0}, 3, 0, 0x6, 2}}}}; | |
| c.run.group[2] = 0x01; | |
| c.run.group[3] = 0x11; | |
| c.run.group_len = 4; | |
| std::vector<uint32_t> wrote; | |
| c.bank.onByte = [&](uint32_t argument, uint8_t &, bool &) { | |
| wrote.push_back(argument); | |
| }; | |
| EXPECT_EQ(c.rows(restore, 1), NSM_FPGA_RUN_DONE); | |
| ASSERT_EQ(wrote.size(), 3u); | |
| EXPECT_EQ((wrote[1] >> 8) & 0xff, 0x01u); | |
| EXPECT_EQ((wrote[2] >> 8) & 0xff, 0x11u); | |
| EXPECT_NE(wrote[2] & (NSM_FPGA_I2C_STOP << 16), 0u); | |
| Runner s; | |
| s.run.group_len = 1; | |
| EXPECT_EQ(s.rows(restore, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_FALSE(s.run.fence); | |
| } | |
| TEST(fpgaRun, byteReadCapturesAndNackFailsWithoutFence) | |
| { | |
| Runner r; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_BYTE, | |
| .station = 1, | |
| .u = {.i2c = {0x50, 0x0e, NSM_FPGA_I2C_START}}}, | |
| {.op = NSM_FPGA_OP_BYTE, | |
| .station = 1, | |
| .capture = NSM_FPGA_BUF_ITEM, | |
| .u = {.i2c = {0x50, 0x00, | |
| NSM_FPGA_I2C_READ | NSM_FPGA_I2C_STOP | | |
| NSM_FPGA_I2C_READ_NACK}}}}; | |
| r.bank.byteData = 0x5a; | |
| EXPECT_EQ(r.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 4u); | |
| ASSERT_EQ(r.run.item_len, 1u); | |
| EXPECT_EQ(r.run.item[0], 0x5a); | |
| EXPECT_TRUE(r.run.wrote_any); | |
| EXPECT_EQ(r.session.data_sequence, 2u); | |
| Runner n; | |
| n.bank.nackBytes = true; | |
| EXPECT_EQ(n.rows(rows, 2), NSM_FPGA_RUN_FAILED); | |
| EXPECT_FALSE(n.run.fence); | |
| EXPECT_NE(std::strstr(n.run.error, "nack"), nullptr); | |
| EXPECT_EQ(n.bank.exchanges, 2u); | |
| // a stale answer from the previous byte op is read past | |
| Runner f; | |
| f.bank.foreignByteReplies = 1; | |
| f.bank.byteData = 0x33; | |
| EXPECT_EQ(f.rows(rows, 2), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(f.bank.exchanges, 5u); | |
| EXPECT_EQ(f.run.item[0], 0x33); | |
| } | |
| TEST(fpgaRun, faultForeignNoSessionAndLostSetFence) | |
| { | |
| Runner fault; | |
| fault.bank.faultCompletions = 1; | |
| EXPECT_EQ(fault.rows(readCalibration, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(fault.run.fence); | |
| EXPECT_NE(std::strstr(fault.run.error, "faulted"), nullptr); | |
| Runner foreign; | |
| foreign.session.grant.session = 9; | |
| nsm_fpga_run_rows(&foreign.run, readCalibration, 1); | |
| // the bank answers with another session's identity | |
| const nsm_fpga_step *step = nullptr; | |
| EXPECT_EQ(nsm_fpga_run_next(&foreign.run, &step), NSM_FPGA_RUN_SEND); | |
| Record sent{}; | |
| foreign.bank.answer(*step, sent); | |
| nsm_fpga_run_reply(&foreign.run, nullptr, false, false); | |
| EXPECT_EQ(nsm_fpga_run_next(&foreign.run, &step), NSM_FPGA_RUN_SEND); | |
| Record reply{}; | |
| foreign.bank.answer(*step, reply); | |
| putLe(reply, 16, 1234, 4); | |
| nsm_fpga_run_reply(&foreign.run, reply.data(), false, false); | |
| EXPECT_EQ(nsm_fpga_run_next(&foreign.run, &step), NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(foreign.run.fence); | |
| EXPECT_NE(std::strstr(foreign.run.error, "foreign"), nullptr); | |
| Runner noSession; | |
| nsm_fpga_run_rows(&noSession.run, readCalibration, 1); | |
| EXPECT_EQ(noSession.bank.drive(noSession.run, 0, 2), | |
| NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(noSession.run.fence); | |
| EXPECT_NE(std::strstr(noSession.run.error, "no session"), nullptr); | |
| Runner lostSet; | |
| nsm_fpga_run_rows(&lostSet.run, readCalibration, 1); | |
| EXPECT_EQ(lostSet.bank.drive(lostSet.run, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(lostSet.run.fence); | |
| /* the request went out, so a lost completion read fences too */ | |
| Runner lostGet; | |
| nsm_fpga_run_rows(&lostGet.run, readCalibration, 1); | |
| EXPECT_EQ(lostGet.bank.drive(lostGet.run, 2), NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(lostGet.run.fence); | |
| EXPECT_EQ(lostGet.session.data_sequence, 0u); | |
| /* a lost status read published nothing and does not fence */ | |
| Runner lostStatus; | |
| nsm_fpga_run_status(&lostStatus.run); | |
| EXPECT_EQ(lostStatus.bank.drive(lostStatus.run, 1), | |
| NSM_FPGA_RUN_FAILED); | |
| EXPECT_FALSE(lostStatus.run.fence); | |
| } | |
| // A bank answering garbage: every job must end within the poll and repeat | |
| // bounds, never read or write past the item and group buffers, and never | |
| // count a garbage reply as a verified data operation | |
| TEST(fpgaRun, randomRepliesEndEveryJobInsideItsBounds) | |
| { | |
| uint32_t seed = 0xc0ffee11u; | |
| auto next = [&seed]() { | |
| seed = seed * 1664525u + 1013904223u; | |
| return seed; | |
| }; | |
| static const nsm_fpga_command rows[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .capture = NSM_FPGA_BUF_ITEM, | |
| .u = {.csr = {0x05000400ULL, 0}}}, | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .poll = NSM_FPGA_POLL_SET, | |
| .capture = NSM_FPGA_BUF_GROUP, | |
| .poll_mask = 0x1, | |
| .u = {.csr = {0x0500045cULL, 0}}}, | |
| {.op = NSM_FPGA_OP_I2C_XFER, | |
| .station = 1, | |
| .capture = NSM_FPGA_BUF_ITEM, | |
| .u = {.xfer = {0x50, {0x00, 0, 0}, 1, 2, 0, 0}}}}; | |
| for (int trial = 0; trial < 300; ++trial) { | |
| Runner r; | |
| nsm_fpga_run_rows(&r.run, rows, 3); | |
| nsm_fpga_run_action action = NSM_FPGA_RUN_SEND; | |
| int guard = 0; | |
| for (; guard < 5000; ++guard) { | |
| const nsm_fpga_step *step = nullptr; | |
| action = nsm_fpga_run_next(&r.run, &step); | |
| if (action == NSM_FPGA_RUN_WAIT) { | |
| continue; | |
| } | |
| if (action != NSM_FPGA_RUN_SEND) { | |
| break; | |
| } | |
| Record reply{}; | |
| for (auto &byte : reply) { | |
| byte = static_cast<uint8_t>(next() >> 24); | |
| } | |
| if ((next() & 3) == 0) { | |
| putLe(reply, 0, NSM_FPGA_PROFILE_IBC2, 4); | |
| } | |
| nsm_fpga_run_reply(&r.run, | |
| step->kind == NSM_FPGA_STEP_READ | |
| ? reply.data() | |
| : nullptr, | |
| false, false); | |
| } | |
| EXPECT_TRUE(guard < 5000); | |
| EXPECT_EQ(action, NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(r.run.item_len <= sizeof(r.run.item)); | |
| EXPECT_TRUE(r.run.group_len <= sizeof(r.run.group)); | |
| EXPECT_EQ(r.session.data_sequence, 0u); | |
| } | |
| } | |
| TEST(fpgaRun, illegalWordIsRefusedBeforeTheWire) | |
| { | |
| Runner r; | |
| static const nsm_fpga_command misaligned[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .u = {.csr = {0x05000401ULL, 0}}}}; | |
| EXPECT_EQ(r.rows(misaligned, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_EQ(r.bank.exchanges, 0u); | |
| EXPECT_FALSE(r.run.fence); | |
| EXPECT_NE(std::strstr(r.run.error, "blocked"), nullptr); | |
| static const nsm_fpga_command quarantine[] = { | |
| {.op = NSM_FPGA_OP_WRITE32, | |
| .station = 37, | |
| .u = {.csr = {0x04ULL, 0x07000019u}}}}; | |
| Runner q; | |
| EXPECT_EQ(q.rows(quarantine, 1), NSM_FPGA_RUN_FAILED); | |
| EXPECT_EQ(q.bank.exchanges, 0u); | |
| } | |
| TEST(fpgaRun, controlAndStatusJobs) | |
| { | |
| Runner r; | |
| nsm_fpga_run_status(&r.run); | |
| EXPECT_EQ(r.bank.drive(r.run), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 1u); | |
| EXPECT_EQ(r.run.authority.epoch, 0x68e7a1c0u); | |
| EXPECT_EQ(r.run.authority.generation, 3u); | |
| nsm_fpga_run_control(&r.run, NSM_FPGA_CTRL_ACQUIRE, 0); | |
| EXPECT_EQ(r.bank.drive(r.run), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 3u); | |
| EXPECT_EQ(r.bank.controls, 1u); | |
| EXPECT_EQ(r.session.control_sequence, 1u); | |
| EXPECT_EQ(r.bank.control[4], NSM_FPGA_CTRL_ACQUIRE); | |
| nsm_fpga_run_control(&r.run, NSM_FPGA_CTRL_RELEASE, 5); | |
| EXPECT_EQ(r.bank.drive(r.run), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.session.control_sequence, 2u); | |
| EXPECT_EQ(r.bank.control[28], 5); | |
| Runner rejected; | |
| rejected.bank.acquireResult = NSM_FPGA_CTRL_BUSY; | |
| nsm_fpga_run_control(&rejected.run, NSM_FPGA_CTRL_ACQUIRE, 0); | |
| EXPECT_EQ(rejected.bank.drive(rejected.run), NSM_FPGA_RUN_FAILED); | |
| EXPECT_TRUE(rejected.run.fence); | |
| EXPECT_NE(std::strstr(rejected.run.error, "rejected"), nullptr); | |
| EXPECT_NE(std::strstr(rejected.run.error, "BUSY"), nullptr); | |
| } | |
| TEST(fpgaRun, recoverCarriesTheEpochAndRefusalDoesNotFence) | |
| { | |
| Runner r; | |
| nsm_fpga_run_recover(&r.run, 0x0102030405060708ULL); | |
| EXPECT_EQ(r.bank.drive(r.run), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 2u); | |
| EXPECT_EQ(r.bank.controls, 1u); | |
| EXPECT_EQ(r.bank.control[4], NSM_FPGA_CTRL_RECOVER); | |
| EXPECT_EQ(r.bank.control[8], 0x08); | |
| EXPECT_EQ(r.bank.control[15], 0x01); | |
| EXPECT_EQ(r.bank.control[16], 0); | |
| EXPECT_EQ(r.bank.control[24], 0); | |
| // No session was used, so its sequence is untouched | |
| EXPECT_EQ(r.session.control_sequence, 0u); | |
| Runner refused; | |
| refused.bank.acquireResult = NSM_FPGA_CTRL_DENIED; | |
| nsm_fpga_run_recover(&refused.run, 0x0102030405060708ULL); | |
| EXPECT_EQ(refused.bank.drive(refused.run), NSM_FPGA_RUN_FAILED); | |
| EXPECT_FALSE(refused.run.fence); | |
| EXPECT_NE(std::strstr(refused.run.error, "recover refused"), nullptr); | |
| EXPECT_NE(std::strstr(refused.run.error, "DENIED"), nullptr); | |
| } | |
| TEST(fpgaRun, activateCarriesTheEpochLikeRecover) | |
| { | |
| Runner r; | |
| nsm_fpga_run_activate(&r.run, 0x0102030405060708ULL); | |
| EXPECT_EQ(r.bank.drive(r.run), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.bank.exchanges, 2u); | |
| EXPECT_EQ(r.bank.control[4], NSM_FPGA_CTRL_ACTIVATE); | |
| EXPECT_EQ(r.bank.control[8], 0x08); | |
| EXPECT_EQ(r.bank.control[16], 0); | |
| EXPECT_EQ(r.session.control_sequence, 0u); | |
| Runner refused; | |
| refused.bank.acquireResult = NSM_FPGA_CTRL_DENIED; | |
| nsm_fpga_run_activate(&refused.run, 0x0102030405060708ULL); | |
| EXPECT_EQ(refused.bank.drive(refused.run), NSM_FPGA_RUN_FAILED); | |
| EXPECT_FALSE(refused.run.fence); | |
| EXPECT_NE(std::strstr(refused.run.error, "activate refused"), nullptr); | |
| } | |
| TEST(fpgaRun, beginItemKeepsTheGroupBufferAndDefaultCapture) | |
| { | |
| Runner r; | |
| static const nsm_fpga_command toGroup[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .capture = NSM_FPGA_BUF_GROUP, | |
| .u = {.csr = {0x05000400ULL, 0}}}}; | |
| r.bank.words[0x05000400ULL] = 0xabcd; | |
| EXPECT_EQ(r.rows(toGroup, 1), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.run.group_len, 4u); | |
| nsm_fpga_run_begin_item(&r.run); | |
| EXPECT_EQ(r.run.group_len, 4u); | |
| EXPECT_EQ(r.run.item_len, 0u); | |
| EXPECT_FALSE(r.run.any_read); | |
| static const nsm_fpga_command plain[] = { | |
| {.op = NSM_FPGA_OP_READ32, | |
| .station = 29, | |
| .u = {.csr = {0x05000400ULL, 0}}}}; | |
| EXPECT_EQ(r.rows(plain, 1), NSM_FPGA_RUN_DONE); | |
| EXPECT_EQ(r.run.item_len, 0u); | |
| nsm_fpga_run_default_capture(&r.run); | |
| ASSERT_EQ(r.run.item_len, 4u); | |
| EXPECT_EQ(r.run.item[0], 0xcd); | |
| EXPECT_EQ(r.run.item[1], 0xab); | |
| nsm_fpga_run_begin_round(&r.run); | |
| EXPECT_EQ(r.run.group_len, 0u); | |
| } | |
| TEST(fpgaRun, theMapRowsRunOnTheBank) | |
| { | |
| // Every rule of the map runs to its end against the scripted bank | |
| const nsm_fpga_map *p0 = nsm_fpga_map_p0(); | |
| for (size_t p = 0; p < p0->part_count; ++p) { | |
| const nsm_fpga_part *part = p0->parts[p]; | |
| for (size_t i = 0; i < part->rule_count; ++i) { | |
| const nsm_fpga_rule *rule = &part->rules[i]; | |
| if (rule->command_count == 0) { | |
| continue; | |
| } | |
| Runner r; | |
| r.bank.words[0x0500045cULL] = 0x1; | |
| r.bank.words[0x08ULL] = 1; | |
| r.bank.words[0x18ULL] = 0; | |
| const auto action = | |
| r.rows(rule->commands, rule->command_count); | |
| // a precheck that the scripted bank does not satisfy | |
| // fails cleanly, everything else runs through | |
| EXPECT_TRUE( | |
| action == NSM_FPGA_RUN_DONE || | |
| (action == NSM_FPGA_RUN_FAILED && !r.run.fence)) | |
| << rule->name << ": " << r.run.error; | |
| } | |
| } | |
| } |