MatchLib
Master.h
1 /*
2  * Copyright (c) 2017-2020, NVIDIA CORPORATION. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License")
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #ifndef __AXI_T_MASTER__
17 #define __AXI_T_MASTER__
18 
19 #include <systemc.h>
20 #include <ac_reset_signal_is.h>
21 
22 #include <axi/axi4.h>
23 #include <nvhls_connections.h>
24 #include <hls_globals.h>
25 
26 #include <queue>
27 #include <string>
28 #include <sstream>
29 #include <vector>
30 #include <map>
31 #include <math.h>
32 #include <boost/assert.hpp>
33 
34 #include <boost/random/mersenne_twister.hpp>
35 #include <boost/random/uniform_int_distribution.hpp>
36 #include <algorithm>
37 
50 struct masterCfg {
51  enum {
52  numWrites = 100,
53  numReads = 100,
54  readDelay = 0,
55  seed = 0,
56  };
57  // enum only supports 32-bit types, so use static 64-bit types as needed (e.g., for >32bit addresses)
58  static const uint64_t addrBoundLower = 0;
59  static const uint64_t addrBoundUpper = 0xFFFFFFFF;
60 };
61 
86 template <typename axiCfg, typename cfg>
87 class Master : public sc_module {
88  BOOST_STATIC_ASSERT_MSG(axiCfg::useWriteResponses || cfg::numReads == 0 || cfg::readDelay != 0,
89  "Must use a substantial read delay if reading without write responses");
90  public:
91  static const int kDebugLevel = 0;
92  typedef axi::axi4<axiCfg> axi4_;
93 
94  typename axi4_::read::template master<> if_rd;
95  typename axi4_::write::template master<> if_wr;
96 
97  sc_in<bool> reset_bar;
98  sc_in<bool> clk;
99 
100  std::map<typename axi4_::Addr, typename axi4_::Data> localMem;
101  std::map<typename axi4_::Addr, NVUINT8 > localMem_wstrb;
102  std::vector<typename axi4_::Addr> validReadAddresses;
103  std::vector<typename axi4_::Addr> validReadAddresses_q;
104  std::vector< long long > validReadAddresses_ctr;
105 
106  static const int bytesPerBeat = axi4_::DATA_WIDTH >> 3;
107  static const bool wResp = axiCfg::useWriteResponses;
108 
109  sc_out<bool> done;
110 
111  SC_CTOR(Master)
112  : if_rd("if_rd"), if_wr("if_wr"), reset_bar("reset_bar"), clk("clk") {
113 
114  SC_THREAD(run);
115  sensitive << clk.pos();
116  async_reset_signal_is(reset_bar, false);
117  }
118 
119  protected:
120  void run() {
121  static const int bytesPerWord = axi4_::DATA_WIDTH >> 3;
122  static const int axiAddrBitsPerWord = nvhls::log2_ceil<bytesPerWord>::val;
123  // Workaround for useBurst=0, which sets ALEN field to 0 width
124  static const int ALEN_W = axiCfg::useBurst != 0 ? axi4_::ALEN_WIDTH : 1;
125  static const int WSTRB_W = axiCfg::useWriteStrobes != 0 ? axi4_::WSTRB_WIDTH : 1;
126  std::queue <typename axi4_::Addr> raddr_queue;
127  std::queue < NVUINTW(ALEN_W) > rlen_queue;
128  std::queue <typename axi4_::Addr> waddr_queue;
129  std::queue < NVUINTW(ALEN_W) > wlen_queue;
130  typename axi4_::Data check_data;
131  NVUINT8 check_data_wstrb;
132  NVUINT8 rcv_data_wstrb;
133  typename axi4_::AddrPayload addr_pld;
134  typename axi4_::ReadPayload data_pld;
135 
136  // Follow the same priority as nvhls_rand: environment, then preprocessor define, then config
137  unsigned int seed = cfg::seed;
138 #ifdef RAND_SEED
139  seed = (RAND_SEED);
140 #endif
141  const char* env_rand_seed = std::getenv("RAND_SEED");
142  if (env_rand_seed != NULL) seed = atoi(env_rand_seed);
143  boost::random::mt19937 gen(seed);
144  boost::random::uniform_int_distribution<uint64_t> random_addr(cfg::addrBoundLower, cfg::addrBoundUpper);
145  boost::random::uniform_int_distribution<> random_wstrb(1, pow(2,WSTRB_W)-1);
146  boost::random::uniform_int_distribution<> random_burstlen(0, axiCfg::maxBurstSize-1);
147  boost::random::uniform_int_distribution<> uniform_rand;
148 
149  typename axi4_::Addr wr_addr = cfg::addrBoundLower;
150  typename axi4_::Data wr_data = 0xf00dcafe12345678;
151  NVUINTW(WSTRB_W) wstrb = ~0;
152  NVUINTW(ALEN_W) wr_len = 0;
153  typename axi4_::Addr rd_addr_next;
154  typename axi4_::Addr rd_addr;
155  NVUINTW(ALEN_W) rd_len;
156  typename axi4_::AddrPayload wr_addr_pld;
157  typename axi4_::WritePayload wr_data_pld;
158  typename axi4_::WRespPayload wr_resp_pld;
159 
160  done = 0;
161  unsigned int numWrites = 0;
162  unsigned int numReads = 0;
163  unsigned int numWritesOfBurst = 0;
164  unsigned int numReadsOfBurst = 0;
165  unsigned int numWriteResponses = 0;
166  unsigned int numReadResponses = 0;
167  bool writeInProgress = false;
168  bool rd_conflict = false;
169  bool wr_conflict = false;
170 
171  if_rd.reset();
172  if_wr.reset();
173 
174  wait(20);
175  while (1) {
176  wait();
177 
178  // READ
179  if (validReadAddresses.size() > 0 && numReads < cfg::numReads) {
180  rd_addr_next = validReadAddresses[uniform_rand(gen) % validReadAddresses.size()];
181  addr_pld.addr = rd_addr_next;
182  if (axiCfg::useBurst) {
183  if (uniform_rand(gen) % 2 == 0) { // 50% of reads are bursts (less once valid addresses are disallowed)
184  rd_len = random_burstlen(gen);
185  } else {
186  rd_len = 0;
187  }
188  addr_pld.len = rd_len;
189  }
190  wr_conflict = false;
191  for (unsigned int i=0; i<(rd_len+1); i++) {
192  if (!localMem.count(rd_addr_next+bytesPerBeat*i)) {
193  wr_conflict = true; // Not actually a conflict, but the read should be cancelled nonetheless
194  }
195  }
196  std::ostringstream ms3;
197  ms3 << "\nError @" << sc_time_stamp() << " from " << name()
198  << ": Testharness attempted to read an address that it never wrote to"
199  << ", read_addr=" << hex << rd_addr_next
200  << endl;
201  BOOST_ASSERT_MSG( localMem.count(rd_addr_next), ms3.str().c_str() );
202  for (unsigned int j=0; j<waddr_queue.size(); j++) {
203  if ((rd_addr_next+bytesPerBeat*rd_len) >= waddr_queue.front() && rd_addr_next <= waddr_queue.front())
204  wr_conflict = true;
205  waddr_queue.push(waddr_queue.front());
206  waddr_queue.pop();
207  }
208  if (!wr_conflict) {
209  rd_addr_next = addr_pld.addr;
210  if (if_rd.ar.PushNB(addr_pld)) {
211  CDCOUT(sc_time_stamp() << " " << name() << " Sent read request: ["
212  << addr_pld << "]"
213  << endl, kDebugLevel);
214  numReads++;
215  for (unsigned int i=0; i<(rd_len+1); i++) {
216  raddr_queue.push(rd_addr_next);
217  rd_addr_next += bytesPerBeat;
218  }
219  rlen_queue.push(rd_len);
220  }
221  }
222  }
223  if (if_rd.r.PopNB(data_pld)) {
224  rd_addr = raddr_queue.front();
225  if (axiCfg::useWriteStrobes) {
226  bool checked_one = false;
227  for (int i=0; i<axi4_::WSTRB_WIDTH; i++) {
228  if (localMem_wstrb.count(rd_addr+i)) {
229  rcv_data_wstrb = nvhls::get_slc<8>(data_pld.data,8*i);
230  check_data_wstrb = localMem_wstrb[rd_addr+i];
231  std::ostringstream msg;
232  msg << "\nError @" << sc_time_stamp() << " from " << name()
233  << ": Incorrect read data response"
234  << ", base_addr=" << hex << rd_addr
235  << ", byte=" << i
236  << ", data=" << hex << rcv_data_wstrb
237  << ", expected=" << hex << check_data_wstrb
238  << std::endl;
239  BOOST_ASSERT_MSG( check_data_wstrb == rcv_data_wstrb, msg.str().c_str() );
240  checked_one = true;
241  }
242  }
243  std::ostringstream msg3;
244  msg3 << "\nError @" << sc_time_stamp() << " from " << name()
245  << ": Testbench did not check any bytes of a read response" << std::endl;
246  BOOST_ASSERT_MSG( checked_one, msg3.str().c_str() );
247  } else {
248  check_data = localMem[rd_addr];
249  std::ostringstream msg;
250  msg << "\nError @" << sc_time_stamp() << " from " << name()
251  << ": Incorrect read data response"
252  << ", addr=" << hex << rd_addr
253  << ", data=" << hex << data_pld.data.to_uint64()
254  << ", expected=" << hex << check_data.to_uint64()
255  << std::endl;
256  BOOST_ASSERT_MSG( check_data == data_pld.data , msg.str().c_str() );
257  }
258  std::ostringstream ms2;
259  ms2 << "\nError @" << sc_time_stamp() << " from " << name()
260  << ": Read response protocol error"
261  << ", rresp=" << data_pld.resp.to_uint64()
262  << std::endl;
263  BOOST_ASSERT_MSG( (data_pld.resp == axi4_::Enc::XRESP::OKAY) |
264  (data_pld.resp == axi4_::Enc::XRESP::EXOKAY), ms2.str().c_str() );
265  CDCOUT(sc_time_stamp() << " " << name() << " Received correct read response: ["
266  << data_pld << "]"
267  << endl, kDebugLevel);
268  raddr_queue.pop();
269  if (numReadsOfBurst++ == rlen_queue.front()) {
270  numReadsOfBurst = 0;
271  numReadResponses++;
272  rlen_queue.pop();
273  }
274  }
275 
276  // WRITE
277  wr_addr_pld.addr = wr_addr;
278  wr_data_pld.data = wr_data;
279  wr_data_pld.wstrb = wstrb;
280  wr_addr_pld.len = wr_len;
281  if (!writeInProgress && numWrites < cfg::numWrites) {
282  rd_conflict = false;
283  for (unsigned int j=0; j<raddr_queue.size(); j++) {
284  if ((wr_addr+bytesPerBeat*wr_len) >= raddr_queue.front() && wr_addr <= raddr_queue.front())
285  rd_conflict = true;
286  raddr_queue.push(raddr_queue.front());
287  raddr_queue.pop();
288  }
289  if (!rd_conflict) {
290  if (if_wr.aw.PushNB(wr_addr_pld)) {
291  CDCOUT(sc_time_stamp() << " " << name() << " Sent write request: ["
292  << wr_addr_pld << "]"
293  << endl, kDebugLevel);
294  for (unsigned int i=0; i<(wr_len+1); i++) {
295  waddr_queue.push(wr_addr+bytesPerBeat*i);
296  // If the address was already written to once, it needs to be removed from the list of valid addresses
297  // because a read after this second write could return incorrect data
298  validReadAddresses.erase(std::remove(validReadAddresses.begin(), validReadAddresses.end(), waddr_queue.front()), validReadAddresses.end());
299  if (!wResp) {
300  ptrdiff_t pos = find(validReadAddresses_q.begin(), validReadAddresses_q.end(), waddr_queue.front()) - validReadAddresses_q.begin();
301  int size = validReadAddresses_q.size();
302  // If the address is already in the queue, just reset the counter
303  if (pos < size) {
304  validReadAddresses_ctr[pos] = cfg::readDelay + 20*i;
305  } else {
306  validReadAddresses_q.push_back(waddr_queue.front());
307  validReadAddresses_ctr.push_back(cfg::readDelay + 20*i);
308  }
309  waddr_queue.pop();
310  }
311  }
312  wlen_queue.push(wr_len);
313  writeInProgress = true;
314  }
315  }
316  }
317  if (writeInProgress) {
318  if (axiCfg::useBurst) {
319  if (numWritesOfBurst == (wr_len)) {
320  wr_data_pld.last = 1;
321  } else {
322  wr_data_pld.last = 0;
323  }
324  }
325  if (if_wr.w.PushNB(wr_data_pld)) {
326  CDCOUT(sc_time_stamp() << " " << name() << " Sent write data:"
327  << " addr=" << hex << wr_addr
328  << " data=[" << wr_data_pld << "]"
329  << " beat=" << dec << numWritesOfBurst
330  << endl, kDebugLevel);
331  if (axiCfg::useWriteStrobes) {
332  for (int i=0; i<axi4_::WSTRB_WIDTH; i++) {
333  if (wr_data_pld.wstrb[i] == 1) {
334  localMem_wstrb[wr_addr+i] = nvhls::get_slc<8>(wr_data_pld.data, 8*i);
335  }
336  }
337  }
338  localMem[wr_addr] = wr_data_pld.data; // Need to keep track of base addresses, even for wstrb case
339  if (++numWritesOfBurst == (wr_len+1)) { // Whole burst is done
340  wr_addr = (random_addr(gen) >> axiAddrBitsPerWord) << axiAddrBitsPerWord; // Keep all requests word-aligned
341  if (axiCfg::useBurst) {
342  if (uniform_rand(gen) % 5 == 0) { // 20% of writes are bursts
343  wr_len = random_burstlen(gen);
344  if (wr_addr + bytesPerBeat*wr_len > cfg::addrBoundUpper) wr_len = 0;
345  } else {
346  wr_len = 0;
347  }
348  }
349  writeInProgress = false;
350  numWrites++;
351  numWritesOfBurst = 0;
352  } else { // Only this beat is done
353  wr_addr += bytesPerBeat;
354  }
355  wr_data = 0xf00dcafe12345678
356  ^ ((static_cast<typename axi4_::Data>(wr_addr)) << 16) // Typically touches bits 47:16
357  ^ uniform_rand(gen); // Touches the 32 LSBs
358  wr_data.set_slc(axi4_::DATA_WIDTH-8,NVUINT8(uniform_rand(gen))); // Touches the 8 MSBs, in case of wide data words
359  if (axiCfg::useWriteStrobes) {
360  if (uniform_rand(gen) % 5 == 0) { // 20% of writes have nonuniform strobe
361  wstrb = random_wstrb(gen);
362  } else {
363  wstrb = ~0;
364  }
365  }
366  }
367  }
368  if (if_wr.b.PopNB(wr_resp_pld)) {
369  std::ostringstream msg;
370  msg << "\nError @" << sc_time_stamp() << " from " << name()
371  << ": Write response protocol error"
372  << ", bresp=" << wr_resp_pld.resp.to_uint64()
373  << ", addr=" << hex << waddr_queue.front()
374  << std::endl;
375  BOOST_ASSERT_MSG( (wr_resp_pld.resp == axi4_::Enc::XRESP::OKAY) |
376  (wr_resp_pld.resp == axi4_::Enc::XRESP::EXOKAY), msg.str().c_str() );
377  CDCOUT(sc_time_stamp() << " " << name() << " Received write response"
378  << endl, kDebugLevel);
379  numWriteResponses++;
380  for (unsigned int i=0; i<wlen_queue.front()+1; i++) {
381  validReadAddresses_q.push_back(waddr_queue.front());
382  validReadAddresses_ctr.push_back(cfg::readDelay + 20*i);
383  waddr_queue.pop();
384  }
385  wlen_queue.pop();
386  }
387  if (numWrites == cfg::numWrites &&
388  numReads == cfg::numReads &&
389  (numWriteResponses == numWrites || !wResp) &&
390  numReadResponses == numReads)
391  done = 1;
392  //DCOUT(sc_time_stamp() << " from " << name() << " numWrites: " << dec << numWrites << " numWriteResponses: " << numWriteResponses << " numReads: " << numReads << " numReadResponses: " << numReadResponses << endl);
393  std::ostringstream msg;
394  msg << "\nError @" << sc_time_stamp() << " from " << name()
395  << ": Number of write responses exceeded total number of writes"
396  << ", numWriteResponses=" << dec << numWriteResponses
397  << std::endl;
398  BOOST_ASSERT_MSG( numWriteResponses <= cfg::numWrites, msg.str().c_str() );
399  std::ostringstream ms2;
400  ms2 << "\nError @" << sc_time_stamp() << " from " << name()
401  << ": Number of read responses exceeded total number of reads"
402  << ", numReadResponses=" << dec << numReadResponses
403  << std::endl;
404  BOOST_ASSERT_MSG( numReadResponses <= cfg::numReads, msg.str().c_str() );
405 
406  // If there are no write responses we still want to test reads.
407  // Finesse this by enforcing a fixed delay after a write until reads can be issued to that address.
408  for (unsigned int i=0; i<validReadAddresses_q.size(); i++) {
409  if (validReadAddresses_ctr[i] >= 0) {
410  if (validReadAddresses_ctr[i] == 0) {
411  validReadAddresses.push_back(validReadAddresses_q[i]);
412  }
413  validReadAddresses_ctr[i]--;
414  }
415  }
416  //DCOUT("validReadAddresses: ");
417  //for (typename std::vector< sc_uint<axi4_::ADDR_WIDTH> >::const_iterator q = validReadAddresses.begin(); q != validReadAddresses.end(); ++q)
418  // std::cout << *q << ' ';
419  //DCOUT(endl);
420 
421  }
422  }
423 };
424 
425 #endif
An example config for the AXI master.
Definition: Master.h:50
A struct composed of the signals associated with an AXI write response.
Definition: axi4.h:236
NVUINTW(Wrapped< T >::width) TypeToNVUINT(T in)
Convert Type to NVUINT.
Definition: TypeToBits.h:115
A struct composed of the signals associated with AXI write data.
Definition: axi4.h:279
A struct composed of the signals associated with AXI read and write requests.
Definition: axi4.h:108
A struct composed of the signals associated with an AXI read response.
Definition: axi4.h:180
#define CDCOUT(x, y)
Definition: hls_globals.h:73
The base axi4 class parameterized according a valid config.
Definition: axi4.h:58
Compute Celing of log2 of a constant.
Definition: nvhls_int.h:174
An AXI master that generates random traffic for use in a testbench.
Definition: Master.h:87