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  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();
99  if (!rvsdg::is<llvm::PointerType>(*type))
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 
127 extern "C" void reference_load(void *addr, uint64_t width) {
128 }
129 
130 extern "C" void reference_store(void *addr, uint64_t width) {
131 }
132 
133 extern uint64_t main_time;
134 uint64_t clock_cycles = 0;
135 extern std::unique_ptr<mmio_t> master;
136 
137 extern V_NAME *top;
138 #if VM_TRACE_FST
139 extern VerilatedFstC *tfp;
140 #elif VM_TRACE_VCD
141 extern VerilatedVcdC *tfp;
142 #endif // VM_TRACE
143 
144 void 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::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)