//-----------------------------------------------------------------------------
// (c) Copyright 2005 Actel Corporation
//
// name:		tb_user_corespi.v
// function:	User Testbench for CoreSPI, instantiates 2 cores, one operating
//				in master mode, the other in slave mode
// comments:	best viewed with tabstops set to "4"
// history:		8/01/03  - TFB created
//
// Rev:			2.1 24Jan05 TFB - Production
// 
//
//-----------------------------------------------------------------------------
// Description:
//    The testbench, instantiates two SPI cores. The first is programmed
//    as a master and the second is programmed as a slave.
//    The SPI busses of the two cores are connected together to establish
//    communication between the two cores.
//    The testbench, checks the communication for all CPOL, CPHA combinations,
//    for a single transmission rate. To check for other transmission rates,
//    set the value of the generic CLOCK_SEL to a different value - the various
//    supported values of CLOCK_SEL are shown below:
//         CLOCK_SEL      SCK frequency         CLOCK_SEL      SCK frequency
//           "000"         pclk / 2             "100"         pclk / 32
//           "001"         pclk / 4             "101"         pclk / 64
//           "010"         pclk / 8             "110"         pclk / 128
//           "011"         pclk / 16            "111"         pclk / 256
//-----------------------------------------------------------------------------
`timescale 1ns / 1ps

module tb_vlog;

// constants ...
// Choose clock select frequency (see comments in header above)
parameter CLOCK_SEL		= 1;
// number of bytes to tx/rx between master/slave
parameter TXRX_BYTES	= 16;
// set this to 1 to transmit lsb first, 0 to transmit msb first
parameter TX_LSB_FIRST	= 0;
// System Clock cycle (in ns)
parameter SYS_CLK_CYCLE	= 30;
// Family:
parameter FAMILY      = 17;

// SIGNAL Declarations
wire			GND_sig;
reg				pclk;
reg				presetn;
reg				cpol;
reg				cpha;
wire			m_sck;
wire			m_miso;
wire			m_mosi;
wire	[7:0]	m_ss;
wire			s_sck;
wire			s_miso;
wire			s_mosi;
wire			s_ss;
wire			enable_master1;
wire			enable_master2;
wire			enable_slave1;
wire			enable_slave2;

reg   [7:0] pwdata1;
reg   [7:0] pwdata2;
wire  [7:0] prdata1;
wire  [7:0] prdata2;
reg   [1:0] paddr1;
reg   [1:0] paddr2;

reg         penable1;
reg         penable2;
reg         psel1;
reg         psel2;
reg         pwrite1;
reg         pwrite2;

wire			interrupt1;
wire			interrupt2;
wire			tx_reg_empty1;
wire			tx_reg_empty2;
wire			rx_data_ready1;
wire			rx_data_ready2;
reg		[2:0]	clocksel;
reg				lsb_first;
integer			cyc;
integer			simerrors;

// timeout in case something catastrophic happens
parameter TIMEOUT	= 8191;

// string related signals
reg [8*79:1]	init_str_mem [0:11];
reg [8*79:1]	dash_str,uline_str,copyright_str,tb_name_str,
				tb_ver_str,pound_str,lsb_str,msb_str;

// initialize strings
initial
begin: init_strings

dash_str		=
"-----------------------------------------------------------------------------";
uline_str		=
"_____________________________________________________________________________";
pound_str		=
"#############################################################################";
copyright_str	=
"(c) Copyright 2005 Actel Corporation. All rights reserved.";
tb_name_str		=
"User Testbench for: CoreSPI";
tb_ver_str		=
"Version: 2.1 24Jan05                                                         ";

// initialization of testbench string
init_str_mem[00]	= "";
init_str_mem[01]	= "";
init_str_mem[02]	= uline_str;
init_str_mem[03]	= "";
init_str_mem[04]	= copyright_str;
init_str_mem[05]	= "";
init_str_mem[06]	= tb_name_str;
init_str_mem[07]	= tb_ver_str;
init_str_mem[08]	= uline_str;
init_str_mem[09]	= "";
init_str_mem[10]	= "";
init_str_mem[11]	= "";

lsb_str				= "LSB";
msb_str				= "MSB";
end

//-------------------------------------------------------------------

assign GND_sig = 0;

// generate the system clock
initial cyc	= SYS_CLK_CYCLE;
initial pclk = 0;
always #(cyc/2) pclk = ~pclk;

//-------------------------------------------------------------------

// instantiating CORESPI 1, (Master only)
CORESPI #(FAMILY,1,0) corespi_master (
	.PCLK(pclk),.PRESETN(presetn),.m_sck(m_sck),.m_miso(m_miso),
	.m_mosi(m_mosi),.m_ss(m_ss),.s_sck(GND_sig),.s_miso(),
	.s_mosi(GND_sig),.s_ss(GND_sig),.enable_master(enable_master1),
	.enable_slave(enable_slave1),.interrupt(interrupt1),
	.tx_reg_empty(tx_reg_empty1),.rx_data_ready(rx_data_ready1),
	.PSEL(psel1), .PENABLE(penable1), .PWRITE(pwrite1), .PADDR({paddr1,2'b00}), .PWDATA(pwdata1), .PRDATA(prdata1)
);
assign s_sck	= m_sck;
assign s_mosi	= m_mosi;
assign s_ss		= m_ss[1];

//-------------------------------------------------------------------

// instantiating CORESPI 2, (Slave only)
CORESPI #(FAMILY,0,1) corespi_slave (
	.PCLK(pclk),.PRESETN(presetn),.m_sck(),.m_miso(GND_sig),
	.m_mosi(),.m_ss(),.s_sck(s_sck),.s_miso(s_miso),.s_mosi(s_mosi),
	.s_ss(s_ss),.enable_master(enable_master2),.enable_slave(enable_slave2),
  .interrupt(interrupt2),.tx_reg_empty(tx_reg_empty2),
	.rx_data_ready(rx_data_ready2),
	.PSEL(psel2), .PENABLE(penable2), .PWRITE(pwrite2), .PADDR({paddr2,2'b00}), .PWDATA(pwdata2), .PRDATA(prdata2)
	
);
assign m_miso	= s_miso;

//-------------------------------------------------------------------


// Primary stimuli
initial
begin: primary_proc
integer i,j,k,l,t;
integer divnum;
reg	[8*79:1]	lmsb_str;
reg	[7:0]		 dtmp;
reg	[7:0]		 dtmp_mx;

	$timeformat(-9, 2, " ns", 3);

	i			= 0;
	j			= 0;
	k			= 0;
	l			= 0;
	t			= 0;
	divnum		= 2;
	dtmp		= 8'b0;
	simerrors	= 0;

	// print out copyright info, testbench version, name of testbench, etc.
	for (i=0;i<12;i=i+1)
		$display("%0s",init_str_mem[i]);

	clocksel	= CLOCK_SEL;
	divnum		= 2<<CLOCK_SEL;

	cpol		= 0;
	cpha		= 0;
	if (TX_LSB_FIRST==1) begin
		lsb_first		= 1;
		lmsb_str		= "LSB";
	end
	else begin
		lsb_first		= 0;
		lmsb_str		= "MSB";
	end

	// loop 4 times for different values of cpol and cpha
	for (j=0;j<4;j=j+1) begin
		// initialize signals
		presetn			= 0;
		pwdata1		= 8'b0;
		pwdata2		= 8'b0;
		paddr1		= 2'b0;
		paddr2		= 2'b0;
		penable1  =     0;
		penable2  =     0;
		pwrite1				= 0;
		pwrite2				= 0;
		// synch to falling edge of clock
		@ (posedge pclk);
		@ (negedge pclk);
		#(cyc * 2);
		checksig(m_mosi,"m_mosi",0);
		checksig(m_ss,"m_ss",8'hff);
		checksig(s_miso,"s_miso",0);
		checksig(enable_master1,"enable_master1",0);
		checksig(enable_master2,"enable_master2",0);
		checksig(enable_slave1,"enable_slave1",0);
		checksig(enable_slave2,"enable_slave2",0);
		checksig(prdata1,"prdata1",8'h00);
		checksig(prdata2,"prdata2",8'h00);
		checksig(interrupt1,"interrupt1",0);
		checksig(interrupt2,"interrupt2",0);
		checksig(tx_reg_empty1,"tx_reg_empty1",0);
		checksig(tx_reg_empty2,"tx_reg_empty2",0);
		checksig(rx_data_ready1,"rx_data_ready1",0);
		checksig(rx_data_ready2,"rx_data_ready2",0);
		// Disable SPI, Clear errors
		#(cyc * 1);
		cpha			= 1'b1 & (j>>1);
		cpol			= 1'b1 & j;
		#(cyc * 1);
		$display("%0s",dash_str);
		$display(
		"Testing CoreSPI Tx/Rx (%0d bytes) with:%0s first,CPHA=%0d,CPOL=%0d",
		TXRX_BYTES,lmsb_str,cpha,cpol);
		$display("Clock selector set to: %0d (source clock divided by %0d)",
		CLOCK_SEL,divnum);
		$display("%0s",dash_str);
		#(cyc * 4);
		presetn			= 1;
		#(cyc * 2);
		// read the various registers to verify reset values
		apb_read1(2'b00,8'h00);
		apb_read2(2'b00,8'h00);
		apb_read1(2'b01,8'h00);
		apb_read2(2'b01,8'h00);
		apb_read1(2'b10,8'h00);
		apb_read2(2'b10,8'h00);
		apb_read1(2'b11,8'h00);
		apb_read2(2'b11,8'h00);
		// setup CoreSPI master (enable interrupt pin, master mode,
		// lsb/msb first,
		// cpha(0: 0 degree, no delay; 1: 90 degree delay),
		// cpol(0: act.high clock, 1: act.low clock),
		// clock rate selection
		dtmp			= {2'b11,lsb_first,cpha,cpol,clocksel};
		#(cyc * 1);
		apb_write1(2'b01,dtmp);
		// do a readback to verify written values
		apb_read1(2'b01,dtmp);
		// setup CoreSPI slave (enable interrupt pin, slave mode,
		// lsb/msb first,
		// cpha(0: 0 degree, no delay; 1: 90 degree delay),
		// cpol(0: act.high clock, 1: act.low clock),
		// clock rate selection
		dtmp = {2'b10,lsb_first,cpha,cpol,clocksel};
		#(cyc * 1);
		apb_write2(2'b01,dtmp);
		// do a readback to verify written values
		apb_read2(2'b01,dtmp);
		// Enable SPI, Clear errors
		apb_write1(2'b10,8'h81);
		apb_write2(2'b10,8'h81);
		#(cyc * 1);
		// enable_master1 should be high, enable_slave1 should be low,
		// enable_master2 should be low, enable_slave2 should be high,
		checksig(enable_master1,"enable_master1",1);
		checksig(enable_slave1,"enable_slave1",0);
		checksig(enable_master2,"enable_master2",0);
		checksig(enable_slave2,"enable_slave2",1);
		// CoreSPI master selects to communicate with slave on select line 1
		apb_write1(2'b11,8'h02);
		// send bytes from master to slave (have slave send complement)
		for (i=0;i<TXRX_BYTES;i=i+1) begin
			dtmp	= i;
			#(cyc * 1);
			if (lsb_first)
				dtmp_mx	= ~{dtmp[0],dtmp[1],dtmp[2],dtmp[3],
							dtmp[4],dtmp[5],dtmp[6],dtmp[7]};
			else
				dtmp_mx	= ~dtmp;
			#(cyc * 1);
			// Write data to be transmitted from CoreSPI master to CoreSPI slave
			apb_write1(2'b00,dtmp);
			// Write complement for CoreSPI slave to reply
			//(flip around if lsb first for CoreSPI slave tx to CoresSPI master)
			apb_write2(2'b00,dtmp_mx);
                        if (i==0) begin
                        // first transmission, tx reg expected to be full:
                          apb_read1(2'b10,8'h8C);
                        end
                        else
                        begin
  			  // read master status reg (SPI enabled, "000", master mode busy
    			  // transmitting, tx reg not empty, rx data not ready, no error)
                          apb_read1(2'b10,8'h88); 
                        end
			// Wait for CoreSPI master to receive data
			// (put in timeout in case rx_data_ready1 never happens ...)
			begin: tloop1
			for (t=0;t<TIMEOUT+1;t=t+1) begin
				#(cyc * 1);
				if (rx_data_ready1) begin
					// read master status reg (SPI enabled,"000",
					// master mode not busy transmitting, tx reg empty,
					// rx data ready, no error)
					apb_read1(2'b10,8'h86);
					// rx data available, so read it
					apb_read1(2'b00,~dtmp);
					disable tloop1;
				end
				if (t == TIMEOUT) begin
					$display("*** Timed out waiting for rx_data_ready1.");
					simerrors = simerrors + 1;
				end
			end
			end
			// Wait for CoreSPI slave to receive data
			// (put in timeout in case rx_data_ready2 never happens ...)
			begin: tloop2
			for (t=0;t<TIMEOUT+1;t=t+1) begin
				#(cyc * 1);
				if (rx_data_ready2) begin
					// read slave status reg (SPI enabled,"000",
					// master mode not busy transmitting, tx reg empty,
					// rx data ready, no error)
					apb_read2(2'b10,8'h86);
					// rx data available, so read it
					apb_read2(2'b00,~dtmp_mx);
					disable tloop2;
				end
				if (t == TIMEOUT) begin
					$display(
					"*** Timed out waiting for rx_data_ready2.");
					simerrors = simerrors + 1;
				end
			end
			end
			#(cyc * 1);
		end
	end

	// print out number of simulation errors (if any) at end of sim
	$display(" ");
	$display("%0s",pound_str);
	$display("All Tests complete.");
	$display("%0s",pound_str);
	$display(" ");
	$display("%0s",uline_str);
	$display(" ");
	$display("Number of simulation mis-match errors encountered: %0d",
	simerrors);
	$display(" ");
	$display("Done with simulation.");
	$display("%0s",uline_str);
	$display(" ");
	$display(" ");
	#(cyc * 1);
	$finish;

end


//----------------------------------------------------------------------
// tasks
//----------------------------------------------------------------------

// ----------- check the value of a signal or bus (up to 128 bits) ---------

task checksig;
input [127:0] d;
input [8*17:1] sig_name;
input [127:0] v;
begin
	$timeformat(-9, 2, " ns", 3);
	if (d != v)
	begin
		$display("");
		$display("%0s",pound_str);
		$display("ERROR!!! Mismatch on signal %0s",sig_name);
		$display("At time: %0t",$time);
		$display("Expected value was: 0x%0x, observed value is: 0x%0x",v,d);
		$display("%0s",pound_str);
		$display("");
		simerrors = simerrors + 1;
	end
end
endtask






// ***** These emulate an APB write *****
task apb_write1;
input [1:0] a;
input [7:0] d;
begin
  //$display("CPU Write %04x = %04x",a,d);
  @(negedge pclk);  
  pwrite1  = 1'b0;
  @(negedge pclk);  
  penable1  = 1'b0;
  psel1  = 1'b1;
  paddr1 = a;
  pwdata1  = d;  
  pwrite1  = 1'b1;
  @(negedge pclk);  
  penable1  = 1'b1;
  @(negedge pclk);  
  pwrite1  = 1'b0;
  penable1  = 1'b0;
  psel1  = 1'b0;
end
endtask

task apb_write2;
input [1:0] a;
input [7:0] d;
begin
//  $display("APB Write %04x = %04x",a,data);
  @(negedge pclk);  
  pwrite2  = 1'b0;
  @(negedge pclk);  
  penable2  = 1'b0;
  psel2  = 1'b1;
  paddr2 = a;
  pwdata2  = d;  
  pwrite2  = 1'b1;
  @(negedge pclk);  
  penable2  = 1'b1;
  @(negedge pclk);  
  pwrite2  = 1'b0;
  penable2  = 1'b0;
  psel2  = 1'b0;
end
endtask



// ***** APB READS *****
task apb_read1;
input [1:0] a;
input [7:0] d;
begin
  @(negedge pclk);  
  pwrite1  = 1'b1;
  @(negedge pclk);  
  penable1  = 1'b0;
  psel1  = 1'b1;
  paddr1 = a;
  pwrite1  = 1'b0;
  @(negedge pclk);  
  penable1  = 1'b1;
  @(negedge pclk);
  pwrite1  = 1'b1;
  penable1  = 1'b0;
  psel1  = 1'b0;
  checksig(prdata1,"APB Read Data Bus",d);
//  $display("APB Read %04x = %04x",address,data);
end
endtask

task apb_read2;
input [1:0] a;
input [7:0] d;
begin
  @(negedge pclk);  
  pwrite2  = 1'b1;
  @(negedge pclk);  
  penable2  = 1'b0;
  psel2  = 1'b1;
  paddr2 = a;
  pwrite2  = 1'b0;
  @(negedge pclk);  
  penable2  = 1'b1;
  @(negedge pclk);
  pwrite2  = 1'b1;
  penable2  = 1'b0;
  psel2  = 1'b0;
//  $display("APB Read %04x = %04x",address,data);
  checksig(prdata2,"APB Read Data Bus",d);
end
endtask






endmodule // tb_vlog
