Jlm
Loading...
Searching...
No Matches
VerilatorHarnessAxi.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2025 David Metz <david.c.metz@ntnu.no>
3 * Copyright 2025 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
4 * See COPYING for terms of redistribution.
5 */
6
10
11#include <sstream>
12
13namespace jlm::hls
14{
15
16std::string
18{
19 std::ostringstream cpp;
20 const auto & kernel = *get_hls_lambda(rm);
21 const auto & function_name =
22 dynamic_cast<llvm::LlvmLambdaOperation &>(kernel.GetOperation()).name();
23
24 // The request and response parts of memory queues
25 const auto mem_reqs = get_mem_reqs(kernel);
26 const auto mem_resps = get_mem_resps(kernel);
27 JLM_ASSERT(mem_reqs.size() == mem_resps.size());
28
29 // All inputs that are not memory queues
30 const auto reg_args = get_reg_args(kernel);
31
32 // Extract info about the kernel's function signature in C
35
36 cpp << R"(
37#include "mmio.h"
38#include "mm.h"
39#include <memory>
40#include <cassert>
41#include <cmath>
42#include <verilated.h>
43
44#include "Vtop.h"
45
46#if VM_TRACE_FST
47#include <verilated_fst_c.h>
48#elif VM_TRACE_VCD
49#include <verilated_vcd_c.h>
50#endif // VM_TRACE
51
52#include <cstdint>
53#include <cassert>
54#include <iostream>
55#include "verilator_sim.h"
56#include "RhlsAxi.h"
57std::unique_ptr<mm_magic_t> memories[)"
58 << mem_reqs.size() << R"(];
59// ======== Entry point for calling kernel from host device (C code) ========
60extern "C" )"
61 << c_return_type.value_or("void") << " " << function_name << "(" << c_params << ")" << R"(
62{
63 // initialize memories before verilator
64 master = std::make_unique<mmio_t>(CTRL_BEAT_BYTES);)"
65 << std::endl;
66 size_t m = 0;
67 for (size_t i = 0; i < kernel.GetOperation().type().Arguments().size(); ++i)
68 {
69 if (rvsdg::is<llvm::PointerType>(*kernel.GetOperation().type().Arguments()[i].get()))
70 {
71 if (m < mem_reqs.size())
72 {
73 const auto res_bundle = util::assertedCast<const BundleType>(mem_resps[m]->Type().get());
74 auto size = JlmSize(&*res_bundle->get_element_type("data")) / 8;
75 cpp << " memories[" << m << "] = std::make_unique<mm_magic_t>();" << std::endl;
76 cpp << " memories[" << m << "]->init((uint8_t *) a" << i << ", 1UL << 31, " << size
77 << ", 64, MEMORY_LATENCY);" << std::endl;
78 m++;
79 }
80 }
81 }
82 // TODO: handle globals/ctxvars and ports without argument
83 cpp << R"(
84 verilator_init(0, nullptr);
85
86 wait_cycles(10);
87
88 // initialize struct with control addresses
89 RHLSAXI_substruct_create
90 // wait for accelerator to be ready
91 while (!read(RHLSAXI_substruct->ready));
92
93 // use relative addressing for now - i.e. skip pointer arguments
94)";
95 // TODO: handle 64 bit parameters & floats
96 for (size_t i = 0; i < num_c_params; ++i)
97 {
98 auto type = kernel.GetOperation().type().Arguments()[i].get();
100 {
101 JLM_ASSERT(JlmSize(type) <= 32);
102 cpp << " write(RHLSAXI_substruct->i_data_" << i << ", a" << i << ");" << std::endl;
103 }
104 }
105 // TODO: handle return value
106 cpp << R"(
107
108 // start the accelerator
109 write(RHLSAXI_substruct->start, 1);
110 size_t start_cycles = get_cycles();
111
112 // wait for computation to be done
113 while (!read(RHLSAXI_substruct->done));
114 size_t end_cycles = get_cycles();
115)";
116 if (c_return_type.has_value())
117 cpp << "auto result = read(RHLSAXI_substruct->o_data_0);" << std::endl;
118 cpp << R"(
119 std::cerr << "Accelerator took " << end_cycles - start_cycles << " cycles" << std::endl;
120 write(RHLSAXI_substruct->finish, 1);
121)";
122 if (c_return_type.has_value())
123 cpp << "return *(" << c_return_type.value() << "*)&result;" << std::endl;
124 cpp << R"(
125}
126
127extern "C" void reference_load(void *addr, uint64_t width) {
128}
129
130extern "C" void reference_store(void *addr, uint64_t width) {
131}
132
133extern uint64_t main_time;
134uint64_t clock_cycles = 0;
135extern std::unique_ptr<mmio_t> master;
136
137extern V_NAME *top;
138#if VM_TRACE_FST
139extern VerilatedFstC *tfp;
140#elif VM_TRACE_VCD
141extern VerilatedVcdC *tfp;
142#endif // VM_TRACE
143
144void tick() {
145 mmio_t *m;
146 assert(m = dynamic_cast<mmio_t *>(master.get()));
147
148 // ASSUMPTION: All models have *no* combinational paths through I/O
149 // Step 1: Clock lo -> propagate signals between DUT and software models
150 AXI_LITE_SLAVE_CONNECT_VERILATOR_POS(top, io_s_ctrl, m, CTRL_BEAT_BYTES)
151
152)";
153 size_t m_read = 0, m_write = 0;
154 for (size_t i = 0; i < mem_reqs.size(); i++)
155 {
156 const auto req_bundle = util::assertedCast<const BundleType>(mem_reqs[i]->Type().get());
157 const auto res_bundle = util::assertedCast<const BundleType>(mem_resps[i]->Type().get());
158 auto size = JlmSize(&*res_bundle->get_element_type("data")) / 8;
159 const auto has_write = req_bundle->get_element_type("write") != nullptr;
160 if (has_write)
161 {
162 cpp << " AXI_FULL_MM_CONNECT_VERILATOR_POS(top, io_m_write_" << m_write++ << ", memories["
163 << i << "], " << size << ")" << std::endl;
164 }
165 else
166 {
167 cpp << " AXI_FULL_READ_ONLY_MM_CONNECT_VERILATOR_POS(top, io_m_read_" << m_read++
168 << ", memories[" << i << "], " << size << ")" << std::endl;
169 }
170 }
171 cpp << R"(
172 top->eval();
173#if VM_TRACE
174 if (tfp)
175 tfp->dump((double)main_time);
176#ifdef TRACE_FLUSH
177 tfp->flush();
178#endif
179#endif // VM_TRACE
180 main_time += 5;
181
182 top->clock = 0;
183 top->eval(); // This shouldn't do much
184#if VM_TRACE
185 if (tfp)
186 tfp->dump((double)main_time);
187#ifdef TRACE_FLUSH
188 tfp->flush();
189#endif
190#endif // VM_TRACE
191 main_time += 5;
192
193 // Step 2: Clock high, tick all software models and evaluate DUT with posedge
194 AXI_LITE_SLAVE_CONNECT_VERILATOR_POS_EDGE(top, io_s_ctrl, m)
195
196)";
197 m_read = 0;
198 m_write = 0;
199 for (size_t i = 0; i < mem_reqs.size(); i++)
200 {
201 const auto bundle = util::assertedCast<const BundleType>(mem_reqs[i]->Type().get());
202 const auto has_write = bundle->get_element_type("write") != nullptr;
203 if (has_write)
204 {
205 cpp << " AXI_FULL_MM_CONNECT_VERILATOR_POS_EDGE(top, io_m_write_" << m_write++
206 << ", memories[" << i << "])" << std::endl;
207 }
208 else
209 {
210 cpp << " AXI_FULL_READ_ONLY_MM_CONNECT_VERILATOR_POS_EDGE(top, io_m_read_" << m_read++
211 << ", memories[" << i << "])" << std::endl;
212 }
213 }
214 cpp << R"(
215 top->clock = 1;
216 top->eval();
217 clock_cycles++;
218}
219)";
220
221 return cpp.str();
222}
223
224} // namespace jlm::hls
std::vector< rvsdg::RegionResult * > get_mem_reqs(const rvsdg::LambdaNode &lambda)
Definition base-hls.hpp:93
std::vector< rvsdg::RegionArgument * > get_reg_args(const rvsdg::LambdaNode &lambda)
Definition base-hls.hpp:112
static int JlmSize(const jlm::rvsdg::Type *type)
Definition base-hls.cpp:110
const rvsdg::LambdaNode * get_hls_lambda(llvm::LlvmRvsdgModule &rm)
Definition base-hls.cpp:136
std::vector< rvsdg::RegionArgument * > get_mem_resps(const rvsdg::LambdaNode &lambda)
Definition base-hls.hpp:75
std::string GetText(llvm::LlvmRvsdgModule &rm) override
#define JLM_ASSERT(x)
Definition common.hpp:16
std::tuple< size_t, std::string, std::string > GetParameterListAsC(const rvsdg::LambdaNode &kernel)
std::optional< std::string > GetReturnTypeAsC(const rvsdg::LambdaNode &kernel)
NodeType * TryGetOwnerNode(const rvsdg::Input &input) noexcept
Checks if this is an input to a node of specified type.
Definition node.hpp:872