File size: 7,683 Bytes
0ba037b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | // liblitepcie stand-in backed by a Verilator model of bitacc_pcie_core.
//
// Links with the unmodified host program (bitacc_pcie.c) so its CSR accesses,
// descriptor encoding, padding and result checking run against the RTL:
// - litepcie_readl / litepcie_writel act on the CSR map the LiteX SoC
// generates (64-bit registers split most-significant word first);
// - DMA write buffers are streamed into the core's 128-bit input, result beats
// are collected into DMA_BUFFER_SIZE read buffers.
// The DMA stand-in offers one descriptor per cycle and takes one result beat per
// cycle, with no PCIe latency. At exit it reports the clock cycles the DMA
// phases took (MOCK_REPORT_CYCLES), i.e. the engine's own throughput.
// Only the subset of the library the host program uses is implemented.
#include "Vbitacc_pcie_core.h"
#include "verilated.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <deque>
#include <sys/time.h>
#include <vector>
extern "C" {
#include "liblitepcie.h"
}
#ifndef MOCK_LANES
#define MOCK_LANES 4
#endif
#ifndef MOCK_WPL
#define MOCK_WPL 64
#endif
#ifndef MOCK_MATCH
#define MOCK_MATCH 1
#endif
#ifndef MOCK_MATCH_LANES
#define MOCK_MATCH_LANES MOCK_LANES
#endif
static Vbitacc_pcie_core *core;
static uint64_t cycles;
static uint64_t dma_cycles; // cycles spent in litepcie_dma_process
static uint64_t dma_in, dma_out; // descriptors in, result beats out
static uint32_t host_word;
static uint64_t host_wdata;
static void tick()
{
core->clk = 0; core->eval();
core->clk = 1; core->eval();
cycles++;
}
static void ensure_core()
{
if (core) return;
core = new Vbitacc_pcie_core;
core->reset = 1; core->in_valid = 0; core->out_ready = 0;
core->host_write_stb = 0; core->host_read_stb = 0;
for (int i = 0; i < 4; i++) tick();
core->reset = 0;
tick();
}
// ------------------------------------------------------------------ CSRs
extern "C" uint32_t litepcie_readl(int, uint32_t addr)
{
ensure_core();
tick();
switch (addr) {
case CSR_BITACC_HOST_RDATA_ADDR: return (uint32_t)(core->host_rdata >> 32);
case CSR_BITACC_HOST_RDATA_ADDR + 4: return (uint32_t)core->host_rdata;
case CSR_BITACC_STATUS_ADDR:
return (core->idle << CSR_BITACC_STATUS_IDLE_OFFSET)
| (core->host_busy << CSR_BITACC_STATUS_HOST_BUSY_OFFSET)
| (core->host_rdata_valid << CSR_BITACC_STATUS_RDATA_VALID_OFFSET);
case CSR_BITACC_OPS_ACCEPTED_ADDR: return core->ops_accepted;
case CSR_BITACC_RESULTS_SENT_ADDR: return core->results_sent;
case CSR_BITACC_CONFIG_ADDR:
return (MOCK_LANES << CSR_BITACC_CONFIG_LANES_OFFSET)
| (MOCK_WPL << CSR_BITACC_CONFIG_WORDS_PER_LANE_OFFSET);
case CSR_BITACC_VERSION_ADDR: return 3; // FORMAT_VERSION in bitacc_litefury.py
case CSR_BITACC_FEATURES_ADDR:
return MOCK_MATCH << CSR_BITACC_FEATURES_MATCH_OFFSET
| (MOCK_MATCH ? MOCK_MATCH_LANES : 0) << CSR_BITACC_FEATURES_MATCH_LANES_OFFSET;
default:
fprintf(stderr, "mock: read of unmapped CSR 0x%x\n", addr);
return 0xdeadbeef;
}
}
extern "C" void litepcie_writel(int, uint32_t addr, uint32_t val)
{
ensure_core();
switch (addr) {
case CSR_BITACC_HOST_WORD_ADDR: host_word = val; break;
case CSR_BITACC_HOST_WDATA_ADDR: host_wdata = (host_wdata & 0xffffffffull) | ((uint64_t)val << 32); break;
case CSR_BITACC_HOST_WDATA_ADDR + 4: host_wdata = (host_wdata & ~0xffffffffull) | val; break;
case CSR_BITACC_HOST_CTRL_ADDR:
// CSRStorage pulse fields: one-cycle strobes with the stored word/data.
core->host_word = host_word;
core->host_wdata = host_wdata;
core->host_write_stb = (val >> CSR_BITACC_HOST_CTRL_WRITE_OFFSET) & 1;
core->host_read_stb = (val >> CSR_BITACC_HOST_CTRL_READ_OFFSET) & 1;
tick();
core->host_write_stb = 0;
core->host_read_stb = 0;
break;
default:
fprintf(stderr, "mock: write of unmapped CSR 0x%x\n", addr);
}
tick();
}
// ------------------------------------------------------------------ DMA
static std::deque<std::vector<char>> to_card; // filled write buffers
static std::vector<char> pending_write; // buffer handed to the user
static bool write_handed;
static std::deque<std::vector<char>> to_host; // completed result buffers
static std::vector<char> current_read;
static std::vector<char> results; // partial result buffer
static size_t in_pos; // byte position in to_card.front()
extern "C" int litepcie_dma_init(struct litepcie_dma_ctrl *dma, const char *, uint8_t)
{
ensure_core();
dma->fds.fd = 3;
return 0;
}
extern "C" void litepcie_dma_cleanup(struct litepcie_dma_ctrl *)
{
if (dma_cycles)
fprintf(stderr, "mock: DMA phases %llu cycles, %llu descriptors in, %llu result beats out, "
"%.3f cycles/descriptor\n", (unsigned long long)dma_cycles,
(unsigned long long)dma_in, (unsigned long long)dma_out, (double)dma_cycles / (double)dma_in);
}
extern "C" void litepcie_dma_process(struct litepcie_dma_ctrl *dma)
{
if (write_handed) { // the user filled the last write buffer
to_card.push_back(pending_write);
write_handed = false;
}
for (int c = 0; c < 20000; c++) {
// input: one 128-bit descriptor per beat, little-endian
core->in_valid = 0;
if (dma->reader_enable && !to_card.empty()) {
const unsigned char *d = (const unsigned char *)to_card.front().data() + in_pos;
for (int w = 0; w < 4; w++)
core->in_data[w] = d[4*w] | d[4*w+1] << 8 | d[4*w+2] << 16 | (uint32_t)d[4*w+3] << 24;
core->in_valid = 1;
}
core->out_ready = dma->writer_enable ? 1 : 0;
core->clk = 0; core->eval();
bool in_fire = core->in_valid && core->in_ready;
bool out_fire = core->out_valid && core->out_ready;
unsigned char beat[16];
if (out_fire)
for (int w = 0; w < 4; w++)
for (int b = 0; b < 4; b++) beat[4*w + b] = (core->out_data[w] >> (8*b)) & 0xff;
core->clk = 1; core->eval();
cycles++;
dma_cycles++;
dma_in += in_fire;
dma_out += out_fire;
if (in_fire) {
in_pos += 16;
if (in_pos == DMA_BUFFER_SIZE) { to_card.pop_front(); in_pos = 0; dma->reader_sw_count++; }
}
if (out_fire) {
results.insert(results.end(), (char *)beat, (char *)beat + 16);
if (results.size() == DMA_BUFFER_SIZE) { to_host.push_back(results); results.clear(); }
}
if (to_card.empty() && !core->out_valid && core->idle) break;
}
}
extern "C" char *litepcie_dma_next_write_buffer(struct litepcie_dma_ctrl *)
{
if (write_handed) { to_card.push_back(pending_write); write_handed = false; }
if (to_card.size() >= 8) return nullptr; // bounded like the real ring
pending_write.assign(DMA_BUFFER_SIZE, 0);
write_handed = true;
return pending_write.data();
}
extern "C" char *litepcie_dma_next_read_buffer(struct litepcie_dma_ctrl *dma)
{
if (to_host.empty()) return nullptr;
current_read = to_host.front();
to_host.pop_front();
dma->writer_sw_count++;
return current_read.data();
}
extern "C" int64_t get_time_ms(void)
{
struct timeval tv;
gettimeofday(&tv, nullptr);
return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
extern "C" int mock_cycles(void) { return (int)cycles; }
|