Jlm
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 
13 namespace jlm::hls
14 {
15 
16 std::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
33  const auto c_return_type = GetReturnTypeAsC(kernel);
34  const auto [num_c_params, c_params, c_call_args] = GetParameterListAsC(kernel);
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"
57 std::unique_ptr<mm_magic_t> memories[)"
58  << mem_reqs.size() << R"(];
59 // ======== Entry point for calling kernel from host device (C code) ========
60 extern "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  const auto res_bundle = util::assertedCast<const BundleType>(mem_resps[m]->Type().get());
72  auto size = JlmSize(&*res_bundle->get_element_type("data")) / 8;
73  cpp << " memories[" << m << "] = std::make_unique<mm_magic_t>();" << std::endl;
74  cpp << " memories[" << m << "]->init((uint8_t *) a" << i << ", 1UL << 31, " << size
75  << ", 64, MEMORY_LATENCY);" << std::endl;
76  m++;
77  }
78  }
79  // TODO: handle globals/ctxvars and ports without argument
80  cpp << R"(
81  verilator_init(0, nullptr);
82 
83  wait_cycles(10);
84 
85  // initialize struct with control addresses
86  RHLSAXI_substruct_create
87  // wait for accelerator to be ready
88  while (!read(RHLSAXI_substruct->ready));
89 
90  // use relative addressing for now - i.e. skip pointer arguments
91 )";
92  // TODO: handle 64 bit parameters & floats
93  for (size_t i = 0; i < num_c_params; ++i)
94  {
95  auto type = kernel.GetOperation().type().Arguments()[i].get();
96  if (!rvsdg::is<llvm::PointerType>(*type))
97  {
98  JLM_ASSERT(JlmSize(type) <= 32);
99  cpp << " write(RHLSAXI_substruct->i_data_" << i << ", a" << i << ");" << std::endl;
100  }
101  }
102  // TODO: handle return value
103  cpp << R"(
104 
105  // start the accelerator
106  write(RHLSAXI_substruct->start, 1);
107  size_t start_cycles = get_cycles();
108 
109  // wait for computation to be done
110  while (!read(RHLSAXI_substruct->done));
111  size_t end_cycles = get_cycles();
112 )";
113  if (c_return_type.has_value())
114  cpp << "auto result = read(RHLSAXI_substruct->o_data_0);" << std::endl;
115  cpp << R"(
116  std::cerr << "Accelerator took " << end_cycles - start_cycles << " cycles" << std::endl;
117  write(RHLSAXI_substruct->finish, 1);
118 )";
119  if (c_return_type.has_value())
120  cpp << "return *(" << c_return_type.value() << "*)&result;" << std::endl;
121  cpp << R"(
122 }
123 
124 extern "C" void reference_load(void *addr, uint64_t width) {
125 }
126 
127 extern "C" void reference_store(void *addr, uint64_t width) {
128 }
129 
130 extern uint64_t main_time;
131 uint64_t clock_cycles = 0;
132 extern std::unique_ptr<mmio_t> master;
133 
134 extern V_NAME *top;
135 #if VM_TRACE_FST
136 extern VerilatedFstC *tfp;
137 #elif VM_TRACE_VCD
138 extern VerilatedVcdC *tfp;
139 #endif // VM_TRACE
140 
141 void tick() {
142  mmio_t *m;
143  assert(m = dynamic_cast<mmio_t *>(master.get()));
144 
145  // ASSUMPTION: All models have *no* combinational paths through I/O
146  // Step 1: Clock lo -> propagate signals between DUT and software models
147  AXI_LITE_SLAVE_CONNECT_VERILATOR_POS(top, io_s_ctrl, m, CTRL_BEAT_BYTES)
148 
149 )";
150  size_t m_read = 0, m_write = 0;
151  for (size_t i = 0; i < mem_reqs.size(); i++)
152  {
153  const auto req_bundle = util::assertedCast<const BundleType>(mem_reqs[i]->Type().get());
154  const auto res_bundle = util::assertedCast<const BundleType>(mem_resps[i]->Type().get());
155  auto size = JlmSize(&*res_bundle->get_element_type("data")) / 8;
156  const auto has_write = req_bundle->get_element_type("write") != nullptr;
157  if (has_write)
158  {
159  cpp << " AXI_FULL_MM_CONNECT_VERILATOR_POS(top, io_m_write_" << m_write++ << ", memories["
160  << i << "], " << size << ")" << std::endl;
161  }
162  else
163  {
164  cpp << " AXI_FULL_READ_ONLY_MM_CONNECT_VERILATOR_POS(top, io_m_read_" << m_read++
165  << ", memories[" << i << "], " << size << ")" << std::endl;
166  }
167  }
168  cpp << R"(
169  top->eval();
170 #if VM_TRACE
171  if (tfp)
172  tfp->dump((double)main_time);
173 #ifdef TRACE_FLUSH
174  tfp->flush();
175 #endif
176 #endif // VM_TRACE
177  main_time += 5;
178 
179  top->clock = 0;
180  top->eval(); // This shouldn't do much
181 #if VM_TRACE
182  if (tfp)
183  tfp->dump((double)main_time);
184 #ifdef TRACE_FLUSH
185  tfp->flush();
186 #endif
187 #endif // VM_TRACE
188  main_time += 5;
189 
190  // Step 2: Clock high, tick all software models and evaluate DUT with posedge
191  AXI_LITE_SLAVE_CONNECT_VERILATOR_POS_EDGE(top, io_s_ctrl, m)
192 
193 )";
194  m_read = 0;
195  m_write = 0;
196  for (size_t i = 0; i < mem_reqs.size(); i++)
197  {
198  const auto bundle = util::assertedCast<const BundleType>(mem_reqs[i]->Type().get());
199  const auto has_write = bundle->get_element_type("write") != nullptr;
200  if (has_write)
201  {
202  cpp << " AXI_FULL_MM_CONNECT_VERILATOR_POS_EDGE(top, io_m_write_" << m_write++
203  << ", memories[" << i << "])" << std::endl;
204  }
205  else
206  {
207  cpp << " AXI_FULL_READ_ONLY_MM_CONNECT_VERILATOR_POS_EDGE(top, io_m_read_" << m_read++
208  << ", memories[" << i << "])" << std::endl;
209  }
210  }
211  cpp << R"(
212  top->clock = 1;
213  top->eval();
214  clock_cycles++;
215 }
216 )";
217 
218  return cpp.str();
219 }
220 
221 } // namespace jlm::hls
std::vector< rvsdg::RegionArgument * > get_reg_args(const rvsdg::LambdaNode &lambda)
Definition: base-hls.hpp:112
std::vector< rvsdg::RegionResult * > get_mem_reqs(const rvsdg::LambdaNode &lambda)
Definition: base-hls.hpp:93
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
Lambda operation.
Definition: lambda.hpp:30
#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)
static std::string type(const Node *n)
Definition: view.cpp:255