// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Copyright (c) 2005 by Lattice Semiconductor Corporation
// --------------------------------------------------------------------
//
// Permission:
//
//   Lattice Semiconductor grants permission to use this code for use
//   in synthesis for any Lattice programmable logic product.  Other
//   use of this code, including the selling or duplication of any
//   portion is strictly prohibited.
//
// Disclaimer:
//
//   This VHDL or Verilog source code is intended as a design reference
//   which illustrates how these types of functions can be implemented.
//   It is the user's responsibility to verify their design for
//   consistency and functionality through the use of formal
//   verification methods.  Lattice Semiconductor provides no warranty
//   regarding the use or functionality of this code.
//
// --------------------------------------------------------------------
//
//                     Lattice Semiconductor Corporation
//                     5555 NE Moore Court
//                     Hillsboro, OR 97214
//                     U.S.A
//
//                     TEL: 1-800-Lattice (USA and Canada)
//                          408-826-6000 (other locations)
//
//                     web: http://www.latticesemi.com/
//                     email: techsupport@latticesemi.com
//
// --------------------------------------------------------------------
//
//  Project:           FCRAM memory test
//  File:              fcram_test.v
//  Title:             fcram_test
//  Description:       This module implements state machines that
//                     perform the DDR memory test through Lattice IP
//                     core FCRAM-ONE-E2-N1.
//
// --------------------------------------------------------------------
//
// Revision History :
// --------------------------------------------------------------------
// $Log: fcram_test.v,v $
// Revision 1.0  2005-06-28 13:29:11-07  jhsin
// Initial revision
//
// --------------------------------------------------------------------

`include "fcram_params.v"
`timescale 100ps / 10ps

//
//  This module implements a memory test algrorithm into logic for testing
//  the EC/XP advanced evaluation board FCRAM memory through Lattice's
//  FCRAM I IP core.  The code of this module is modified from the one used
//  for testing the SO-DIMM DDR memory (through the pipelined DDR IP core).
//  Most of the signals and ports names in this module are kept the same.
//  The connections between this module and the FCRAM IP core are:
//
//  Inputs:
//    pll_clk         -> From FRRAM IP core "pll_clk"
//    rst_n           -> From FRRAM IP core "sys_resetn"
//    cmd_rdy         -> From the inverted version of FRRAM IP core "usr_acko"
//    data_rdy        -> From FRRAM IP core "usr_dreqo"
//    read_data_valid -> From FRRAM IP core "usr_dvalo"
//    read_data       -> From FRRAM IP core "usr_datao"
//
//  Outputs:
//    init_start      -> Inverted version connect to FRRAM IP core "usr_reset"
//    usr_cmdi        -> Connect to FRRAM IP core "usr_cmdi"
//    addr            -> Connect to FRRAM IP core "usr_addri"
//    write_data      -> Connect to FRRAM IP core "usr_datai"
//
module fcram_test (
   //-------INPUTS
   waited_200us,
   init_done,
   pll_clk,
   rst_n,
   cmd_rdy,
   data_rdy,
   read_data_valid,
   read_data,

   //-------OUTPUTS
   init_start,
   test_data,
   fsm2,
   datacheck,
   latched_data,
   number_of_tests_passed,
   number_of_tests_failed,
   usr_cmdi,
   cmd_valid,
   addr,
   write_data
);

input                    waited_200us;
input                    init_done;
input                    pll_clk;
input                    rst_n;
input                    cmd_rdy;
input                    data_rdy;
input                    read_data_valid;
input  [`DSIZE-1:0]      read_data;

output                   init_start;
output [7:0]             test_data;
output                   fsm2;
output [1:0]             datacheck;
output [15:0]            latched_data;
output [3:0]             number_of_tests_passed;
output [3:0]             number_of_tests_failed;
output [2:0]             usr_cmdi;
output                   cmd_valid;
output [`ADDR_WIDTH-1:0] addr;
output [`DSIZE-1:0]      write_data;

reg                      init_start;
reg [2:0]                cmd;
reg                      cmd_valid;
reg [`ADDR_WIDTH-1:0]    addr;
reg [`DSIZE-1:0]         write_data;
reg [15:0]               latched_data;

reg                      inc_test_addr;
reg [`ADDR_WIDTH-2:0]    test_addr;
reg [7:0]                test_data;
reg [1:0]                datacheck;
reg [3:0]                number_of_tests_passed;
reg [3:0]                number_of_tests_failed;

reg                      read_data_valid_delay;
reg                      data_rdy_delay;
reg [6:0]                wait_count;

reg                      cmd_valid_delay;
wire                     start_asserting_cmd;
reg                      cmd_assert;

//
//  Assert SELFR command when cmd_valid is low (when fsm1 = fsm1_change_test_data), otherwise,
//  assert WRITE or READ commands when cmd_assert is high and assert NOP when cmd_assert is low.
//
assign usr_cmdi = cmd_valid ? (cmd_assert ? cmd : `CMD_NOP) : `CMD_SELFR;

//
// Wait 200µs after the power is applied and the clocks are running before
// asserting the "init_start" signal.
// Signal "init_start" should be kept asserted unless there is a system reset (~rst_n).
// The FCRAM IP core does not have a "init_done" to indicate the completion of the initialization.
// Therefore, an timer delayer "init_done" signal is generated on the top level to be used as a 
// kickoff of the FSM1 main state machine.
//
always @(posedge pll_clk or negedge rst_n)
begin
   if (~rst_n) begin
      init_start <= 1'b0;
   end
   else if (waited_200us) begin
      init_start <= 1'b1;
   end
end

//
// Delays singnals
//    (1) read_data_valid (used in the READs), and
//    (2) data_rdy (used in the WRITEs)
//
always @(posedge pll_clk or negedge rst_n)
begin
   if (~rst_n) begin
      read_data_valid_delay <= 1'b0;
      data_rdy_delay <= 1'b0;
      cmd_valid_delay <= 1'b0;
   end
   else begin
      read_data_valid_delay <= read_data_valid;
      data_rdy_delay <= data_rdy;
      cmd_valid_delay <= cmd_valid;
   end
end

//
// Creating start_asserting_cmd
//   WRITE and READ commands will be asserted at
//     (1) rising edge of cmd_valid : for the first WRITE in every test
//     (2) falling edge of data_rdy (usr_dreqo) : for the subsequent WRITEs and the first READ
//     (3) falling edge of read_data_valid (usr_dvalo): for subsequent READs 
//
assign start_asserting_cmd = ~cmd_valid_delay & cmd_valid                // rising edge of cmd_valid
                             | data_rdy_delay & ~data_rdy                // falling edge of data_rdy
                             | read_data_valid_delay & ~read_data_valid; // falling edge of read_data_valid

//
// Creating cmd_assert
//   WRITE and READ commands will be asserted when start_asserting_cmd is high
//   and be negated when cmd_rdy is low (usr_acko is high).
//
always @(posedge pll_clk or negedge rst_n)
begin
   if (~rst_n) begin
      cmd_assert <= 1'b0;
   end
   else if (cmd_rdy == 1'b0) cmd_assert <= 1'b0;
   else if (start_asserting_cmd == 1'b1) cmd_assert <= 1'b1;
end

//
// FSM1 : main state machine for DDR memory testing
//
reg [4:0] fsm1;
parameter fsm1_idle             = 5'b00001,
          fsm1_write            = 5'b00010,
          fsm1_verify           = 5'b00100,
          fsm1_change_test_data = 5'b01000,
          fsm1_wait_for_cmd_rdy = 5'b10000;

always @(posedge pll_clk or negedge rst_n)
begin
   if (~rst_n) begin
      cmd              <= 3'b0;
      cmd_valid        <= 1'b0;
      addr             <= {(`ADDR_WIDTH){1'b0}};
      write_data       <= {(`DSIZE){1'b0}};
      inc_test_addr    <= 1'b0;
      datacheck        <= 2'b0;
      latched_data     <= 16'b0;
      wait_count       <= 7'h0;
      fsm1             <= fsm1_idle;
   end
   else begin
      case (fsm1)
         fsm1_idle:
            if (init_done) begin
               fsm1 <= fsm1_wait_for_cmd_rdy;
            end
         fsm1_wait_for_cmd_rdy:
            if (cmd_rdy) begin
               // According to the FCRAM IP Core User's Manual:
               //    The usr_addri[26:0] is the user address and is broken down as follows:
               //       usr_addri[26:25] = bank[1:0]
               //       usr_addri[24:10] = row[14:0]
               //       usr_addri[9:0]   = col[9:0]
               //    Note the x8 device needs 8 col bits while the x16 device only needs
               //    7 col bits, so the unused upper col bits need to be zeroed out.
               // For x8 FCRAM device, the unused upper col bits are bit 8 and 9 (addr[9:8]).
               addr <= { test_addr[`ADDR_WIDTH-3:8], 2'b00, test_addr[7:0] };
               write_data <= { test_data, (test_data ^ 8'hff) };
               datacheck <= 2'b0;
               cmd <= `CMD_WRITE;
               cmd_valid <= 1'b1;
               fsm1 <= fsm1_write; // Switch to fsm1_write to start a new test
               inc_test_addr <= 1'b1;
            end
         fsm1_write:
            begin
               addr <= { test_addr[`ADDR_WIDTH-3:8], 2'b00, test_addr[7:0] };
               write_data <= { test_data, (test_data ^ 8'hff) };
               datacheck <= 2'b0;
               if (cmd_rdy & test_addr[`ADDR_WIDTH-2]) begin
                  cmd <= `CMD_READ; // Switch to fsm1_verify to read data back and check if is correct
                  fsm1 <= fsm1_verify;
               end
            end
         fsm1_verify:
            begin
               addr <= { test_addr[`ADDR_WIDTH-3:8], 2'b00, test_addr[7:0] };
               write_data <= { test_data, (test_data ^ 8'hff) }; // Even after switched to read, write_data still
                                                                 // needs to be updated for the last few writes
               if (read_data_valid) begin // Checking readback data
                  latched_data <= read_data[15:0];
                  datacheck <= {|(read_data[15: 8]^(test_data)),
                                |(read_data[ 7: 0]^(test_data ^ 8'hff))};
               end
               if (cmd_rdy & ~test_addr[`ADDR_WIDTH-2]) begin
                  cmd <= `CMD_WRITE;
                  fsm1 <= fsm1_change_test_data; // Switch to fsm1_change_test_data to change test_data for the next test
                  cmd_valid <= 1'b0;
               end
            end
         fsm1_change_test_data:
            begin
               write_data <= { test_data, (test_data ^ 8'hff) };
               if (cmd_rdy & (wait_count[6] == 1'b1)) begin
                  datacheck <= 2'b0;
                  fsm1 <= fsm1_write; // Switch to fsm1_write to start another new test
                  cmd_valid <= 1'b1;
                  wait_count <= 7'h0;
               end
               else begin
                  if (read_data_valid) begin // Even after switched to this state, we still need
                                             // to check data for the last few reads
                     latched_data <= read_data[15:0];
                     datacheck <= {|(read_data[15: 8]^(test_data)),
                                   |(read_data[ 7: 0]^(test_data ^ 8'hff))};
                  end
                  wait_count <= wait_count + 1;
               end
            end
      endcase
   end
end

//
// Creating test_addr
//   The size of test_addr is one more bit than what's required.
//   The extra MSB bit (test_addr[`ADDR_WIDTH-2]) is used to switch between fsm1_write state
//   and fsm1_verify state.
//
always @(posedge pll_clk or negedge rst_n)
begin
   if (~rst_n) begin
      test_addr <= {(`ADDR_WIDTH-1){1'b0}};
   end
   else if (inc_test_addr & (data_rdy | read_data_valid)) begin
      // Advance test_addr for the next READ command or WRITE command
      // Default burst length = 2, therefore increase test_addr by 2
      if (fsm1 != fsm1_change_test_data) test_addr <= test_addr + 2;
   end
end

//
// Creating test_data
//
always @(posedge pll_clk or negedge rst_n)
begin
   if (~rst_n) begin
      test_data <= 8'h01;
   end
   else begin
      if (data_rdy || read_data_valid) begin
         // Rotate test_data for every read command and write command
         // During the same test, test_data will be rotated whenever
         //    (1) test_data is sampled by data_rdy durring the write, or
         //    (2) read_data_valid indicates data is read back (rotate test_data for data checking)
         if (fsm1 != fsm1_change_test_data) test_data <= { test_data[6:0], test_data[7] };
      end
      else if (wait_count == 7'h20) begin // Change "test_data" for the next test
         //
         // test #                =>  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 , 17 18 19 20 21 22 23 24 ...
         // test_data(at addr  0) => 01 fd 04 f7 10 df 40 7f fe 02 fb 08 ef 20 bf 80 , 01 fd 04 f7 10 df 40 7f ...
         // test_data(at addr  2) => 02 fb 08 ef 20 bf 80 fe fd 04 f7 10 df 40 7f 01 , 02 fb 08 ef 20 bf 80 fe ...
         // test_data(at addr  4) => 04 f7 10 df 40 7f 01 fd fb 08 ef 20 bf 80 fe 02 , 04 f7 10 df 40 7f 01 fd ...
         // test_data(at addr  6) => 08 ef 20 bf 80 fe 02 fb f7 10 df 40 7f 01 fd 04 , 08 ef 20 bf 80 fe 02 fb ...
         // test_data(at addr  8) => 10 df 40 7f 01 fd 04 f7 ef 20 bf 80 fe 02 fb 08 , 10 df 40 7f 01 fd 04 f7 ...
         // test_data(at addr  A) => 20 bf 80 fe 02 fb 08 ef df 40 7f 01 fd 04 f7 10 , 20 bf 80 fe 02 fb 08 ef ...
         // test_data(at addr  C) => 40 7f 01 fd 04 f7 10 df bf 80 fe 02 fb 08 ef 20 , 40 7f 01 fd 04 f7 10 df ...
         // test_data(at addr  E) => 80 fe 02 fb 08 ef 20 bf 7f 01 fd 04 f7 10 df 40 , 80 fe 02 fb 08 ef 20 bf ...
         // test_data(at addr 10) => 01 fd 04 f7 10 df 40 7f fe 02 fb 08 ef 20 bf 80 , 01 fd 04 f7 10 df 40 7f ...
         //         :
         //         :
         if ((test_data == 8'h80) || (test_data == 8'h7f)) begin
            test_data <= { test_data[6:0], test_data[7] }; // Rotated test_data
         end
         else begin
            test_data <= { test_data[6:0], test_data[7] } ^ 8'hff; // Rotate test_data and then invert it
         end
      end
   end
end


//
// FSM2 for increaing success/failure counters during the data checking
//
reg fsm2;
parameter fsm2_wait_for_data_check = 1'b0,
          fsm2_check_readback_data = 1'b1;

always @(posedge pll_clk or negedge rst_n)
begin
   if (~rst_n) begin
      number_of_tests_passed <= 4'b0;
      number_of_tests_failed <= 4'b0;
      fsm2 <= fsm2_wait_for_data_check;
   end
   else begin
      case (fsm2)
         fsm2_wait_for_data_check:
            if ((fsm1 == fsm1_write) && (test_addr[`ADDR_WIDTH-2] == 1'b1)) begin
               fsm2 <= fsm2_check_readback_data;
            end
         fsm2_check_readback_data:
            if (wait_count[5]) begin
               // test_data will be changed whenever (wait_count == 7'h20), therefore,
               // if no error found when wait_count[5] goes high (i.e. wait_count >= 7'h20),
               // then increase correct counter, go back to fsm2_wait_for_data_check
               // and wait for next test
               number_of_tests_passed <= number_of_tests_passed + 1;
               fsm2 <= fsm2_wait_for_data_check;
            end
            else if (datacheck[1] | datacheck[0]) begin
               // If there is any error (even just 1 error), increase error counter
               // and go to fsm2_wait_for_data_check for next test
               number_of_tests_failed <= number_of_tests_failed + 1;
               fsm2 <= fsm2_wait_for_data_check;
            end
      endcase
   end
end

endmodule