1//
2// Copyright 2019 Ettus Research, A National Instruments Company
3//
4// SPDX-License-Identifier: LGPL-3.0-or-later
5//
6// Module: sim_axi_ram
7//
8// Description:
9//
10//   Simulation model for a basic AXI4 memory mapped memory. A few notes on its
11//   behavior:
12//
13//     - This model does not reorder requests (regardless of WID/RID). All
14//       requests are evaluated strictly in order.
15//     - The only supported response is OKAY
16//     - This model supports misaligned memory accesses, which cause a
17//       simulation warning.
18//     - A reset does not clear the memory contents
19//     - The memory itself is implemented using an associative array (sparse
20//       matrix) so that large memories can be supported.
21//     - This model is half duplex, meaning read and write data transfers won't
22//       happen at the same time. A new data transfer won't begin until the
23//       previous one has completed.
24//
25
26module sim_axi_ram #(
27  parameter AWIDTH     = 32,
28  parameter DWIDTH     = 64,
29  parameter IDWIDTH    = 2,
30  parameter BIG_ENDIAN = 0,
31  parameter STALL_PROB = 25
32) (
33  input logic s_aclk,
34  input logic s_aresetn,
35
36  // Write Address Channel
37  input  logic [IDWIDTH-1:0] s_axi_awid,
38  input  logic [ AWIDTH-1:0] s_axi_awaddr,
39  input  logic [        7:0] s_axi_awlen,
40  input  logic [        2:0] s_axi_awsize,
41  input  logic [        1:0] s_axi_awburst,
42  input  logic               s_axi_awvalid,
43  output logic               s_axi_awready,
44
45  // Write Data Channel
46  input  logic [  DWIDTH-1:0] s_axi_wdata,
47  input  logic [DWIDTH/8-1:0] s_axi_wstrb,
48  input  logic                s_axi_wlast,
49  input  logic                s_axi_wvalid,
50  output logic                s_axi_wready,
51
52  // Write Response Channel
53  output logic [IDWIDTH-1:0] s_axi_bid,
54  output logic [        1:0] s_axi_bresp,
55  output logic               s_axi_bvalid,
56  input  logic               s_axi_bready,
57
58  // Read Address Channel
59  input  logic [IDWIDTH-1:0] s_axi_arid,
60  input  logic [ AWIDTH-1:0] s_axi_araddr,
61  input  logic [        7:0] s_axi_arlen,
62  input  logic [        2:0] s_axi_arsize,
63  input  logic [        1:0] s_axi_arburst,
64  input  logic               s_axi_arvalid,
65  output logic               s_axi_arready,
66
67  // Read Data Channel
68  output logic [       0:0] s_axi_rid,
69  output logic [DWIDTH-1:0] s_axi_rdata,
70  output logic [       1:0] s_axi_rresp,
71  output logic              s_axi_rlast,
72  output logic              s_axi_rvalid,
73  input  logic              s_axi_rready
74);
75
76  localparam DEBUG = 0;
77
78  // Define a mask that can be used to tell which 4K window is being addressed
79  localparam [AWIDTH-1:0] MASK_4K = {AWIDTH{1'b1}} << 12;
80
81
82  //---------------------------------------------------------------------------
83  // Data Types
84  //---------------------------------------------------------------------------
85
86  typedef enum logic [1:0] { FIXED, INCR, WRAP }            burst_t;
87  typedef enum logic [1:0] { OKAY, EXOKAY, SLVERR, DECERR } resp_t;
88
89  typedef struct packed {
90    longint             count;  // Number of requests to wait for before executing
91    logic [IDWIDTH-1:0] id;
92    logic [AWIDTH-1:0]  addr;
93    logic [8:0]         len;    // Add an extra bit, since actual true length is +1
94    logic [7:0]         size;   // Add extra bits to store size in bytes, instead of clog2(size)
95    burst_t             burst;
96  } req_t;
97
98  // Make the address type an extra bit wide so that we can detect
99  // out-of-bounds accesses easily.
100  typedef bit [AWIDTH:0] addr_t;
101
102  // Data word type
103  typedef logic [DWIDTH-1:0] data_t;
104
105  // Mask to indicate which bits should be written.
106  typedef bit [DWIDTH/8-1:0] mask_t;
107
108
109  //---------------------------------------------------------------------------
110  // Data Structures
111  //---------------------------------------------------------------------------
112
113  byte             memory [addr_t];     // Byte addressable memory
114  mailbox #(req_t) read_req   = new();  // Read request queue
115  mailbox #(req_t) write_req  = new();  // Write request queue
116  mailbox #(req_t) write_resp = new();  // Write response queue
117
118  longint req_count;    // Number of requests received
119  longint compl_count;  // Number of requests completed
120
121
122  //---------------------------------------------------------------------------
123  // External Configuration Interface
124  //---------------------------------------------------------------------------
125
126  int waddr_stall_prob = STALL_PROB;
127  int wdata_stall_prob = STALL_PROB;
128  int wresp_stall_prob = STALL_PROB;
129  int raddr_stall_prob = STALL_PROB;
130  int rdata_stall_prob = STALL_PROB;
131
132  // Set ALL stall probabilities to the same value
133  function void set_stall_prob(int probability);
134    assert(probability >= 0 && probability <= 100) else begin
135      $error("Probability must be from 0 to 100");
136    end
137    waddr_stall_prob = probability;
138    wdata_stall_prob = probability;
139    wresp_stall_prob = probability;
140    raddr_stall_prob = probability;
141    rdata_stall_prob = probability;
142  endfunction : set_stall_prob
143
144  // Set WRITE stall probabilities to the same value
145  function void set_write_stall_prob(int probability);
146    assert(probability >= 0 && probability <= 100) else begin
147      $error("Probability must be from 0 to 100");
148    end
149    waddr_stall_prob = probability;
150    wdata_stall_prob = probability;
151    wresp_stall_prob = probability;
152  endfunction : set_write_stall_prob
153
154  // Set READ stall probabilities to the same value
155  function void set_read_stall_prob(int probability);
156    assert(probability >= 0 && probability <= 100) else begin
157      $error("Probability must be from 0 to 100");
158    end
159    raddr_stall_prob = probability;
160    rdata_stall_prob = probability;
161  endfunction : set_read_stall_prob
162
163  // Set Write Address Channel stall probability
164  function void set_waddr_stall_prob(int probability);
165    assert(probability >= 0 && probability <= 100) else begin
166      $error("Probability must be from 0 to 100");
167    end
168    waddr_stall_prob = probability;
169  endfunction : set_waddr_stall_prob
170
171  // Set Write Data Channel stall probability
172  function void set_wdata_stall_prob(int probability);
173    assert(probability >= 0 && probability <= 100) else begin
174      $error("Probability must be from 0 to 100");
175    end
176    wdata_stall_prob = probability;
177  endfunction : set_wdata_stall_prob
178
179  // Set Write Response Channel stall probability
180  function void set_wresp_stall_prob(int probability);
181    assert(probability >= 0 && probability <= 100) else begin
182      $error("Probability must be from 0 to 100");
183    end
184    wresp_stall_prob = probability;
185  endfunction : set_wresp_stall_prob
186
187  // Set Read Address Channel stall probability
188  function void set_raddr_stall_prob(int probability);
189    assert(probability >= 0 && probability <= 100) else begin
190      $error("Probability must be from 0 to 100");
191    end
192    raddr_stall_prob = probability;
193  endfunction : set_raddr_stall_prob
194
195  // Set Read Data Channel stall probability
196  function void set_rdata_stall_prob(int probability);
197    assert(probability >= 0 && probability <= 100) else begin
198      $error("Probability must be from 0 to 100");
199    end
200    rdata_stall_prob = probability;
201  endfunction : set_rdata_stall_prob
202
203  // Get Write Address Channel stall probability
204  function int get_waddr_stall_prob();
205    return waddr_stall_prob;
206  endfunction : get_waddr_stall_prob
207
208  // Get Write Data Channel stall probability
209  function int get_wdata_stall_prob();
210    return wdata_stall_prob;
211  endfunction : get_wdata_stall_prob
212
213  // Get Write Response Channel stall probability
214  function int get_wresp_stall_prob();
215    return wresp_stall_prob;
216  endfunction : get_wresp_stall_prob
217
218  // Get Read Address Channel stall probability
219  function int get_raddr_stall_prob();
220    return raddr_stall_prob;
221  endfunction : get_raddr_stall_prob
222
223  // Get Read Data Channel stall probability
224  function int get_rdata_stall_prob();
225    return rdata_stall_prob;
226  endfunction : get_rdata_stall_prob
227
228
229
230  //---------------------------------------------------------------------------
231  // Helper Functions
232  //---------------------------------------------------------------------------
233
234  function data_t read_mem(addr_t byte_addr, int num_bytes);
235    data_t data;
236    addr_t incr;
237
238    if (BIG_ENDIAN) begin
239      byte_addr = byte_addr + num_bytes-1;
240      incr      = -1;
241    end else begin
242      incr      = 1;
243    end
244
245    for (int i = 0; i < num_bytes; i++) begin
246      if (byte_addr >= 2**AWIDTH) begin
247        $fatal(1, "Read extends beyond memory range");
248      end
249      if (memory.exists(byte_addr)) data[i*8 +: 8] = memory[byte_addr];
250      else data[i*8 +: 8] = 'X;
251      byte_addr += incr;
252    end
253
254    return data;
255  endfunction : read_mem
256
257
258  function void write_mem(addr_t byte_addr, int num_bytes, data_t data, mask_t mask);
259    addr_t incr;
260
261    if (BIG_ENDIAN) begin
262      byte_addr = byte_addr + num_bytes-1;
263      incr      = -1;
264    end else begin
265      incr      = 1;
266    end
267
268    for (int i = 0; i < num_bytes; i++) begin
269      if (mask[i]) begin
270        if (byte_addr >= 2**AWIDTH) begin
271          $fatal(1, "Write extends beyond memory range");
272        end
273        memory[byte_addr] = data[i*8 +: 8];
274      end
275      byte_addr += incr;
276    end
277  endfunction : write_mem
278
279
280  //---------------------------------------------------------------------------
281  // Write Requests
282  //---------------------------------------------------------------------------
283
284  initial begin : write_req_proc
285    req_t   req;
286    burst_t burst;
287
288    s_axi_awready <= 0;
289
290    forever begin
291      @(posedge s_aclk);
292      if (!s_aresetn) continue;
293
294      if (s_axi_awvalid) begin
295        if (s_axi_awready) begin
296          req.count = req_count;
297          req.id    = s_axi_awid;
298          req.addr  = s_axi_awaddr;
299          req.len   = s_axi_awlen + 1;    // Per AXI4 spec, Burst_length = AxLEN[7:0] + 1
300          req.size  = 2**s_axi_awsize;    // Store as true size in bytes, not clog2(size)
301          req.burst = burst_t'(s_axi_awburst);
302
303          // Check that the request is valid
304          assert (!$isunknown(req)) else begin
305            $fatal(1, "Write request signals are unknown");
306          end
307          assert (s_axi_araddr % (DWIDTH/8) == 0) else begin
308            $warning("Unaligned memory write");
309          end
310          assert (2**s_axi_awsize <= DWIDTH/8) else begin
311            $fatal(1, "AWSIZE must not be larger than DWIDTH");
312          end
313          assert ($cast(burst, s_axi_awburst)) else begin
314            $fatal(1, "Invalid AWBURST value");
315          end
316          assert ((s_axi_awaddr & MASK_4K) ==
317            ((s_axi_awaddr + (s_axi_awlen+1)*(2**s_axi_awsize) - 1) & MASK_4K)) else begin
318            $fatal(1, "Memory write burst crosses 4 KiB boundary");
319          end
320
321          if (DEBUG) begin
322            $display("WRITE REQ: id=%X, addr=%X, len=%X, size=%X, burst=%s, %t, %m",
323              req.id, req.addr, req.len, req.size, req.burst.name, $realtime);
324          end
325
326          req_count++;
327          write_req.put(req);
328        end
329
330        // Randomly deassert ready
331        s_axi_awready <= $urandom_range(99) < waddr_stall_prob ? 0 : 1;
332      end
333    end
334  end : write_req_proc
335
336
337  //---------------------------------------------------------------------------
338  // Read Requests
339  //---------------------------------------------------------------------------
340
341  initial begin : read_req_proc
342    req_t   req;
343    burst_t burst;
344
345    s_axi_arready <= 0;
346
347    forever begin
348      @(posedge s_aclk);
349      if (!s_aresetn) continue;
350
351      if (s_axi_arvalid) begin
352        if (s_axi_arready) begin
353          req.count = req_count;
354          req.id    = s_axi_arid;
355          req.addr  = s_axi_araddr;
356          req.len   = s_axi_arlen + 1;    // Per AXI4 spec, Burst_length = AxLEN[7:0] + 1
357          req.size  = 2**s_axi_arsize;    // Store as true size in bytes, not clog2(size)
358          req.burst = burst_t'(s_axi_arburst);
359
360          // Check that the request is valid
361          assert(!$isunknown(req)) else begin
362            $fatal(1, "Read request signals are unknown");
363          end
364          assert(s_axi_araddr % (DWIDTH/8) == 0) else begin
365            $warning("Unaligned memory read");
366          end
367          assert(2**s_axi_arsize <= DWIDTH/8) else begin
368            $fatal(1, "ARSIZE must not be larger than DWIDTH");
369          end
370          assert ($cast(burst, s_axi_awburst)) else begin
371            $fatal(1, "Invalid ARBURST value");
372          end
373          assert ((s_axi_araddr & MASK_4K) ==
374            ((s_axi_araddr + (s_axi_arlen+1)*(2**s_axi_arsize) - 1) & MASK_4K)) else begin
375            $fatal(1, "Memory read burst crosses 4 KiB boundary");
376          end
377
378          if (DEBUG) begin
379            $display("READ REQ:  id=%X, addr=%X, len=%X, size=%X, burst=%s, %t, %m",
380              req.id, req.addr, req.len, req.size, req.burst.name, $realtime);
381          end
382
383          req_count++;
384          read_req.put(req);
385        end
386
387        // Randomly deassert ready to cause a stall
388        s_axi_arready <= $urandom_range(99) < raddr_stall_prob ? 0 : 1;
389      end
390    end
391  end : read_req_proc
392
393
394  //---------------------------------------------------------------------------
395  // Write Data
396  //---------------------------------------------------------------------------
397
398  initial begin : write_data_proc
399    req_t req;
400    bit [AWIDTH-1:0] addr;
401
402    forever begin
403      // Wait for the next write request
404      s_axi_wready <= 0;
405      write_req.get(req);
406
407      // Wait for previous requests to complete
408      while (compl_count < req.count) begin
409        @(posedge s_aclk);
410        if (!s_aresetn) break;
411      end
412
413      // If reset was asserted, clear the request queue and start over
414      if (!s_aresetn) begin
415        while(write_req.try_get(req));
416        continue;
417      end
418
419      // Iterate over the number of words in the request
420      for (int i = 0; i < req.len; ) begin
421        @(posedge s_aclk);
422        if (!s_aresetn) break;
423
424        // Check if we have a new data word
425        if (s_axi_wvalid) begin
426          if (s_axi_wready) begin
427            // Check the inputs
428            if ($isunknown(s_axi_wstrb)) begin
429              $fatal(1, "WSTRB is unknown");
430            end
431            if ($isunknown(s_axi_wdata)) begin
432              $warning(1, "WDATA is unknown; data will be changed to zero");
433            end
434
435            case (req.burst)
436              FIXED : begin
437                addr = req.addr;
438              end
439              INCR : begin
440                // If the address rolls over, we've reached the end of the
441                // memory and we should stop here.
442                addr = req.addr + i*req.size;
443                if (addr < req.addr) break;
444              end
445              WRAP : begin
446                // Allow roll-over
447                addr = req.addr + i*req.size;
448              end
449            endcase
450
451            write_mem(addr, req.size, s_axi_wdata, s_axi_wstrb);
452
453            if (DEBUG) begin
454              $display("WRITE: count=%3X, ADDR=%X, DATA=%X, SIZE=%X, STRB=%X, %t, %m",
455                i, addr, s_axi_wdata, req.size, s_axi_wstrb, $realtime);
456            end
457
458            i++;
459          end
460
461          // Randomly deassert ready to cause a stall
462          s_axi_wready <= $urandom_range(99) < wdata_stall_prob ? 0 : 1;
463        end
464      end // for
465
466      // If reset was asserted, clear the request queue and start over
467      if (!s_aresetn) begin
468        while(write_req.try_get(req));
469        continue;
470      end
471
472      compl_count++;
473
474      // Enqueue write response
475      write_resp.put(req);
476
477      // Make sure WLAST asserted for the last word. If not we report an error.
478      // Per the AXI4 standard, "a slave is not required to use the WLAST
479      // signal" because "a slave can calculate the last write data transfer
480      // from the burst length AWLEN".
481      if (s_axi_wlast != 1'b1) begin
482        $error("WLAST not asserted on last word of burst");
483      end
484
485    end // forever
486  end : write_data_proc
487
488
489  //---------------------------------------------------------------------------
490  // Write Response
491  //---------------------------------------------------------------------------
492
493  initial begin : write_resp_proc
494    req_t resp;
495    bit [AWIDTH-1:0] addr;
496
497    forever begin
498      s_axi_bid    <= 'X;
499      s_axi_bresp  <= 'X;
500      s_axi_bvalid <= 0;
501
502      // Wait for the next write response
503      write_resp.get(resp);
504      @(posedge s_aclk);
505
506      // If there's a reset, clear the response queue and start over
507      if (!s_aresetn) begin
508        while(write_resp.try_get(resp));
509        continue;
510      end
511
512      // Randomly keep bvalid deasserted for next word to cause a stall
513      if ($urandom_range(99) < wresp_stall_prob) begin
514        do begin
515          @(posedge s_aclk);
516          if (!s_aresetn) break;
517        end while ($urandom_range(99) < wresp_stall_prob);
518
519        // If reset was asserted, clear the response queue and start over
520        if (!s_aresetn) begin
521          while(write_resp.try_get(resp));
522          continue;
523        end
524      end
525
526      // Output the next response
527      s_axi_bid    <= resp.id;
528      s_axi_bresp  <= OKAY;
529      s_axi_bvalid <= 1;
530
531      if (DEBUG) begin
532        $display("WRITE RESP: ID=%X, %t, %m", resp.id, $realtime);
533      end
534
535      // Wait for the response to be accepted
536      do begin
537        @(posedge s_aclk);
538        if (!s_aresetn) break;
539      end while (!s_axi_bready);
540
541      // Output the next response
542      s_axi_bid    <= 'X;
543      s_axi_bresp  <= 'X;
544      s_axi_bvalid <= 0;
545
546      // If reset was asserted, clear the response queue and start over
547      if (!s_aresetn) begin
548        while(write_resp.try_get(resp));
549        continue;
550      end
551    end // forever
552  end : write_resp_proc
553
554
555  //---------------------------------------------------------------------------
556  // Read Data
557  //---------------------------------------------------------------------------
558
559  initial begin : read_data_proc
560    req_t req;
561    bit   [AWIDTH-1:0] addr;
562    logic [DWIDTH-1:0] data;
563
564    forever begin
565      s_axi_rid    <= 'X;
566      s_axi_rdata  <= 'X;
567      s_axi_rresp  <= 'X;
568      s_axi_rlast  <= 'X;
569      s_axi_rvalid <= 0;
570
571      // Wait for the next read request
572      read_req.get(req);
573
574      // Wait for previous requests to complete
575      do begin
576        @(posedge s_aclk);
577        if (!s_aresetn) break;
578      end while (compl_count < req.count);
579
580      // If reset was asserted, clear the request queue and start over
581      if (!s_aresetn) begin
582        while(read_req.try_get(req));
583        continue;
584      end
585
586      for (int i = 0; i < req.len; i++) begin
587        // Randomly keep rvalid deasserted for next word to cause a stall
588        if ($urandom_range(99) < rdata_stall_prob) begin
589          do begin
590            @(posedge s_aclk);
591            if (!s_aresetn) break;
592          end while ($urandom_range(99) < rdata_stall_prob);
593          if (!s_aresetn) break;
594        end
595
596        case (req.burst)
597          FIXED : begin
598            addr = req.addr;
599          end
600          INCR : begin
601            // If the address rolls over, we've reached the end of the memory
602            // and we should stop here.
603            addr = req.addr + i*req.size;
604            if (addr < req.addr) break;
605          end
606          WRAP : begin
607            // Allow roll-over
608            addr = req.addr + i*req.size;
609          end
610        endcase
611
612        // Read the memory
613        data = read_mem(addr, req.size);
614
615        // Output the next word
616        s_axi_rid    <= req.id;
617        s_axi_rdata  <= data;
618        s_axi_rresp  <= OKAY;
619        s_axi_rlast  <= (i == req.len-1);
620        s_axi_rvalid <= 1;
621
622        if (DEBUG) begin
623          $display("READ:  count=%3X, ADDR=%X, DATA=%X, SIZE=%X, %t, %m", i, addr, data, req.size, $realtime);
624        end
625
626        // Wait for the word to be captured
627        do begin
628          @(posedge s_aclk);
629          if (!s_aresetn) break;
630        end while (!s_axi_rready);
631
632        s_axi_rid    <= 'X;
633        s_axi_rdata  <= 'X;
634        s_axi_rresp  <= 'X;
635        s_axi_rlast  <= 'X;
636        s_axi_rvalid <= 0;
637      end // for
638
639      // If reset was asserted, clear the request queue and start over
640      if (!s_aresetn) begin
641        while(read_req.try_get(req));
642      end
643
644      compl_count++;
645
646    end // forever
647  end : read_data_proc
648
649endmodule
650