aboutsummaryrefslogtreecommitdiff
/*
 * A Wishbone SLAVE testing module (a "mock")
 *
 * | *WISHBONE DATASHEET*                                                      |
 * |---------------------------------------------------------------------------|
 * | *Description*                   | *Specification*                         |
 * |---------------------------------+-----------------------------------------|
 * | General description             | mock memory model for test benches      |
 * |---------------------------------+-----------------------------------------|
 * |                                 | SLAVE, pipelined READ/WRITE             |
 * | Supported cycles                |     (WRITE ability can be disabled      |
 * |                                 |     through module parameter)           |
 * |---------------------------------+-----------------------------------------|
 * | Data port, size                 | parametrizable (WORD_SIZE bytes)        |
 * | Data port, granularity          | parametrizable                          |
 * |                                 |     (WORD_SIZE/SEL_LINES bytes)         |
 * | Data port, maximum operand size | same as data port size                  |
 * | Data transfer ordering          | Little endian                           |
 * | Data transfer ordering          | Undefined                               |
 * | Address port, size              | parametrizable (ADR_BITS bits)          |
 * |---------------------------------+-----------------------------------------|
 * | Clock frequency constraints     | NONE                                    |
 * |---------------------------------+-----------------------------------------|
 * |                                 | *Signal name*    | *WISHBONE Equiv.*    |
 * |                                 |------------------+----------------------|
 * |                                 | ACK_O            | ACK_O                |
 * |                                 | ADR_I            | ADR_I()              |
 * | Supported signal list and cross | CLK_I            | CLK_I                |
 * |     reference to equivalent     | DAT_I            | DAT_I()              |
 * |     WISHBONE signals            | DAT_O            | DAT_O()              |
 * |                                 | STB_I            | STB_I                |
 * |                                 | WE_I             | WE_I                 |
 * |                                 | RST_I            | RST_I                |
 * |                                 | STALL_O          | STALL_O              |
 * |---------------------------------+-----------------------------------------|
 * | Special requirements            | Should only be used in simulation,      |
 * |                                 | not synthesizable.                      |
 */

`default_nettype none

`include "messages.vh"

`ifndef SIMULATION
 `error_SIMULATION_not_defined
; /* Cause syntax error */
`endif

module memory_slave_model
  #(
    parameter SLAVE_NR = -1,
    parameter WORD_SIZE = 2, /* in bytes */
    parameter SEL_LINES = 1,
    parameter ADR_BITS = 18,
    /* Changing the following 3 allows us to make it function as ROM */
    parameter WRITABLE = 1,
    parameter WORDS_TO_INITIALIZE = 0,
    parameter INITIAL_CONTENTS_FILE = "some_file.mem"
    )
   (
    output wire 		      ACK_O,
    /* output wire 		      ERR_O, */ /* we might use it one day */
    input wire 			      CLK_I,
    input wire [ADR_BITS - 1 : 0]     ADR_I,
    input wire [8*WORD_SIZE - 1 : 0]  DAT_I,
    output wire [8*WORD_SIZE - 1 : 0] DAT_O,
    input wire [SEL_LINES - 1 : 0]    SEL_I,
    input wire 			      RST_I,
    input wire 			      STB_I,
    input wire 			      WE_I,
    output wire 		      STALL_O
    );

   parameter GRANULARITY = 8 * WORD_SIZE / SEL_LINES; /* in bits */

   /*
    * A simple memory slave should be most useful for testing purposes;
    * WARNING! The 'memory' variable might be referenced from outside the module
    * by testbench code - be careful when changing or removing it
    */
   reg [GRANULARITY - 1 : 0] 	      memory [2**ADR_BITS - 1 : 0];

   /* For random stall times and acknowledge times */
   integer 			      seed;

   parameter MAX_STALL_TIME = 7;
   parameter MAX_ACK_WAIT_TIME = 7;

   reg [2:0] 			      stall_time_left;
   reg [2:0] 			      acknowledge_time_left;

   /* incoming command pipeline */
   reg 				      WE_I_pipeline [15:0];
   reg [ADR_BITS - 1 : 0] 	      ADR_I_pipeline [15:0];
   reg [8*WORD_SIZE - 1 : 0] 	      DAT_I_pipeline [15:0];
   reg [SEL_LINES - 1 : 0] 	      SEL_I_pipeline [15:0];

   reg [3:0] 			      pipeline_oldest_element;
   reg [4:0] 			      pipeline_elements_count;
   wire [3:0] 			      pipeline_index_to_insert;
   assign pipeline_index_to_insert = (pipeline_oldest_element +
				      pipeline_elements_count) % 16;

   reg 				      stall;

   reg 				      WE_I_to_process;
   reg [ADR_BITS - 1 : 0] 	      ADR_I_to_process;
   reg [8*WORD_SIZE - 1 : 0] 	      DAT_I_to_process;
   reg [SEL_LINES - 1 : 0] 	      SEL_I_to_process;

   always @* begin
      if (pipeline_elements_count) begin
	 WE_I_to_process = WE_I_pipeline[pipeline_oldest_element];
	 ADR_I_to_process = ADR_I_pipeline[pipeline_oldest_element];
	 DAT_I_to_process = DAT_I_pipeline[pipeline_oldest_element];
	 SEL_I_to_process = SEL_I_pipeline[pipeline_oldest_element];
      end else begin
	 WE_I_to_process = WE_I;
	 ADR_I_to_process = ADR_I;
	 DAT_I_to_process = DAT_I;
	 SEL_I_to_process = SEL_I;
      end
   end // always @ *

   /* Those are irrelevant if RST_I is high */
   wire 			      pipeline_space_left;
   wire 			      can_accept;
   wire 			      new_command_accepted;
   wire 			      command_available_for_acknowledging;
   wire 			      command_acknowledged;

   assign pipeline_space_left = pipeline_elements_count < 16;
   assign can_accept = pipeline_space_left && stall_time_left == 0;
   assign new_command_accepted = STB_I && can_accept;
   assign command_available_for_acknowledging = pipeline_elements_count ||
						new_command_accepted;
   assign command_acknowledged = acknowledge_time_left == 0 &&
				 command_available_for_acknowledging;


   integer                   i; /* loop counter */
   reg [8*WORD_SIZE - 1 : 0] masked_out_data;

   always @ (SEL_I_to_process or WE_I_to_process or ADR_I_to_process) begin
      for (i = SEL_LINES - 1; i >= 0; i--) begin
	 if (SEL_LINES > 1) begin
	    masked_out_data[8*WORD_SIZE - 1 : GRANULARITY]
	      = masked_out_data[8*WORD_SIZE - GRANULARITY - 1 : 0];
	 end

	 masked_out_data[GRANULARITY - 1 : 0]
	   = SEL_I_to_process[i] ?
	     memory[ADR_I_to_process + i] : {GRANULARITY{1'bx}};
      end
   end // always @ (SEL_I_to_process or WE_I_to_process or ADR_I_to_process)

   /* Finally, drive the outputs */

   /* only drive data outputs for read commands */
   assign DAT_O = (command_acknowledged && !WE_I_to_process) ?
		  masked_out_data : {(8 * WORD_SIZE - 1){1'bx}};

   assign STALL_O = !can_accept;

   assign ACK_O = command_acknowledged;

   initial begin
      pipeline_oldest_element <= 0;
      pipeline_elements_count <= 0;
      seed <= SLAVE_NR;
      stall_time_left <= 0;
      acknowledge_time_left <= 0;

      if (WORDS_TO_INITIALIZE)
	$readmemb(INITIAL_CONTENTS_FILE, memory, 0, WORDS_TO_INITIALIZE - 1);
   end

   integer                   k; /* loop counter */
   reg [8*WORD_SIZE - 1 : 0] masked_in_data;

   always @ (posedge CLK_I) begin
      if (RST_I) begin
	 pipeline_oldest_element <= 0;
	 pipeline_elements_count <= 0;
      end else begin
	 if (new_command_accepted) begin
	    stall_time_left <= $urandom(seed) % (MAX_STALL_TIME + 1);
	    WE_I_pipeline[pipeline_index_to_insert] <= WE_I;
	    ADR_I_pipeline[pipeline_index_to_insert] <= ADR_I;
	    DAT_I_pipeline[pipeline_index_to_insert] <= DAT_I;
	    SEL_I_pipeline[pipeline_index_to_insert] <= SEL_I;
	 end else begin
	    if (stall_time_left)
	      stall_time_left <= stall_time_left - 1;
	 end

	 if (command_acknowledged) begin
	    acknowledge_time_left <= $urandom(seed) % (MAX_ACK_WAIT_TIME + 1);
	    pipeline_oldest_element <= pipeline_oldest_element + 1;

	    if (WE_I_to_process) begin /* Write command */
	       masked_in_data = DAT_I_to_process;

	       for (k = 0; k < SEL_LINES; k++) begin
		  if (SEL_I_to_process[k]) begin
		     if (WRITABLE) begin
			memory[ADR_I_to_process + k]
			  <= masked_in_data[GRANULARITY - 1 : 0];
		     end
		  end else begin
		     masked_in_data[GRANULARITY - 1 : 0] = {GRANULARITY{1'bx}};
		  end

		  if (SEL_LINES > 1) begin
		     masked_in_data
		       = {masked_in_data[GRANULARITY - 1 : 0],
			  masked_in_data[8*WORD_SIZE - 1 : GRANULARITY]};
		  end
	       end // for (k = 0; k < SEL_LINES; k++)

	       if (WRITABLE) begin
		  `DBG(("Slave %0d: write of h%x (%b) at h%x", SLAVE_NR,
			masked_in_data, SEL_I_to_process, ADR_I_to_process));
	       end else begin
		  `DBG(({"Slave %0d: error: write of h%x (%b) at h%x ",
			 "(read-only) memory"}, SLAVE_NR,
			DAT_I_to_process, SEL_I_to_process, ADR_I_to_process));
	       end
	    end else begin /* Read command */
	       `DBG(("Slave %0d: read of h%x (%b) at h%x", SLAVE_NR,
		     DAT_O, SEL_I_to_process, ADR_I_to_process));
	    end
	 end else if (command_available_for_acknowledging) begin
	    acknowledge_time_left <= acknowledge_time_left - 1;
	 end

	 pipeline_elements_count <= pipeline_elements_count +
				    new_command_accepted - command_acknowledged;
      end
   end
endmodule // memory_slave_model