/*******************************************************************************
 Copyright 2007 Actel Corporation.  All rights reserved.
 
 ANY USE OR REDISTRIBUTION IN PART OR IN WHOLE MUST BE HANDLED IN
 ACCORDANCE WITH THE ACTEL LICENSE AGREEMENT AND MUST BE APPROVED
 IN ADVANCE IN WRITING.

 Revision 3.0 April 30, 2007 : v3.0 CoreFFT Release
*******************************************************************************/
`timescale 1 ns/100 ps

/********************** inBuf LOAD ADDRESS GENERATOR **********************/
module inBuf_ldA (clk, clkEn, nGrst, startLoad, ifi_dataRdy, ifo_loadOn, 
                  load_done, ldA, wEn_even, wEn_odd, ldValid);
  parameter PTS   = 256;
  parameter LOGPTS  = 8;

  input clk, clkEn;
  input nGrst; 
  //comes from topSM to reset ldA count & start another loading cycle 
  input startLoad;  
  input ifi_dataRdy; // inData strobe  
  output ifo_loadOn; // tells user the inBuf is ready for new data
  //out; tells topSM the buffer is fully loaded and ready for FFTing
  output load_done;
  output[LOGPTS-1:1] ldA; // load address
  output wEn_even, wEn_odd, ldValid;        
  reg ifo_loadOn;
  wire ldCountLsb_w; // just LSB of the counter below. Counts even/odd samples
  wire closeLoad_w;
  wire cntEn_w = clkEn & ifi_dataRdy;
  wire loadOver_w = closeLoad_w & wEn_odd;
  assign ldValid = ifo_loadOn & ifi_dataRdy;
  assign wEn_even = ~ldCountLsb_w & ldValid;
  assign wEn_odd  =  ldCountLsb_w & ldValid;

  // count samples loaded.  There is PTS samples to load, not PTS/2
  counter #(.WIDTH(LOGPTS), .TC(PTS-1)) ldCount (.clk(clk), 
    .nGrst(nGrst), .rst(startLoad), .cntEn(cntEn_w), .tc(closeLoad_w),
    .Q({ldA, ldCountLsb_w}));
  // A user can stop supplying ifi_dataRdy after loadOver gets high, thus
  // the loadOver can stay high indefinitely.  Shorten it!  ???
  edgeDetect #(.INPIPE(0), .FEDGE(0)) edge_0
      (.clk(clk), .clkEn(clkEn), .in(loadOver_w), .out(load_done));

  always @ (posedge clk or negedge nGrst)   // generate ifo_loadOn:
    if(!nGrst) ifo_loadOn <= 0;  
    else    
      if (clkEn) begin
        if (loadOver_w) ifo_loadOn <= #1 0;
        else if (startLoad) ifo_loadOn <= #1 1;
      end
endmodule

/*************************  outBuf ADDRESS GENERATOR  *************************/
// Implements write/read data generator.  Writes data in bit-reversed
// fashion.  Write address utilizes precisely same timer as inBuf wA
module outBufA (clk, clkEn, nGrst, rst, timer, outBuf_wEn, rdCtl, wA, rA,
                outBuf_rEn, rdValid);
  parameter PTS   = 256;
  parameter LOGPTS  = 8;

  input clk, clkEn; 
  input nGrst, rst;
  input[LOGPTS-2:0] timer; 
  input outBuf_wEn;
  input rdCtl;  //host can slow down results reading by lowering the signal
  output [LOGPTS-2:0] wA;
  output[LOGPTS-1:0] rA;
  output outBuf_rEn, rdValid;
  reg[LOGPTS-2:0] wA;
  reg outBuf_rEn, rdValid;  

  //reverse bits of the timer
  reg[LOGPTS-2:0] reverseBitTimer;
  wire[LOGPTS-2:0] wA_w;
  wire outBufwEnFall_w, rA_TC_w;
  reg preOutBuf_rEn, pipe11,pipe12, pipe21,pipe22, rdCtl_reg; 

  function[LOGPTS-2:0] reverse;
    input [LOGPTS-2:0] in;
    integer i;
    begin
      for (i=0; i<LOGPTS-1; i=i+1)  reverse[i] = in[LOGPTS-2-i];
    end
  endfunction

  assign #1 wA_w = reverse(timer);
  // rA generator.  Detect rear edge of the outBuf wEn
  edgeDetect #(.INPIPE(0), .FEDGE(1)) fedge_0 (.clk(clk), .clkEn(1'b1),
    .in(outBuf_wEn), .out(outBufwEnFall_w));
  // Reset a binary counter on the rear edge
  counter #(.WIDTH(LOGPTS), .TC(PTS-1)) outBuf_rA_0 (.clk(clk), .nGrst(nGrst),
      .rst(rst|outBufwEnFall_w), .cntEn(rdCtl_reg), .tc(rA_TC_w), .Q(rA));

  always @ (posedge clk or negedge nGrst) begin
    // RS FF preOutBuf_rEn
    if(!nGrst) begin
      preOutBuf_rEn <= #1 1'b0;
      wA            <= #1 'b0; 
      outBuf_rEn    <= #1 1'b0;
      rdValid       <= #1 1'b0;
      reverseBitTimer <= #1 'b0;
      pipe11 <= #1 1'b0; pipe12 <= #1 1'b0; 
      pipe21 <= #1 1'b0; pipe22 <= #1 1'b0; 
      rdCtl_reg <= #1 1'b0; 
    end
    else begin
      if (rst|outBuf_wEn|rA_TC_w) preOutBuf_rEn <= #1 1'b0;
        else if (outBufwEnFall_w)  preOutBuf_rEn <= #1 1'b1; 
      wA <= #1 wA_w;
      rdCtl_reg <= #1 rdCtl;
      outBuf_rEn <= #1 pipe12;
      pipe12 <= #1 pipe11;  pipe11 <= #1 preOutBuf_rEn;
      rdValid <= #1 pipe22;     pipe22 <= #1 pipe21;
      pipe21 <= #1 (preOutBuf_rEn & rdCtl_reg);
    end
  end
endmodule

/*******************  inBuf ADDRESS GENERATOR for BFLY DATA  ******************/
// Implements both read and write data generators.  The core utilizes inPlace
// algorithm thus the wA is a delayed copy of the rA
module inBuf_fftA (clk, clkEn, timer, stage, timerTC, lastStage,
  /*Out. fftOn is on when fft is running. fftDone = fall edge of fftOn.
  It tells topSM a FFT cycle is (almost)over, inBuf has been fully
  read/written out.  Use fftOn as wEn for the inBuf  */
                   fftDone,
                   bflyA,      // read or write address
                   swCross);   // switch control
  parameter LOGPTS  = 8;
  parameter LOGLOGPTS  = 3;

  input clk, clkEn, timerTC, lastStage;
  input[LOGPTS-2:0] timer;
  input[LOGLOGPTS-1:0] stage;
  output fftDone, swCross;
  output[LOGPTS-2:0] bflyA;
  reg fftDone;
  reg[LOGPTS-2:0] bflyA;
  reg swCross;

  wire fftDone_w, swCross_w;
  wire[LOGPTS-2:0] bflyA_w;
  wire[LOGPTS-1:0] addrP_w, offsetPQ_w;

  // calculate addrP:
  //  addrP = (count<<1) & (addrMask2>>stage) | count & (addrMask1>>stage);
  assign #1 addrP_w = ((timer<<1) & (~({1'b0, {LOGPTS{1'b1}}} >> stage)) ) |
              (timer & ( {(LOGPTS-1){1'b1} } >> stage ));

  // address offset between P and Q
	// offsetPQ = (1<<(NumberOfStages-1-stage));
  assign #1 offsetPQ_w = ( 1<<(LOGPTS-1) ) >> stage;
  /* rA takes either Paddr or Qaddr value (Qaddr=Paddr+offsetPQ) per clock, i.e.
  at even clk rA=Paddr, at odd clk rA=Qaddr. (Every addr has a pair of data
  samples).  Timer LSB controls which clk is happening now.  The same timer LSB
  controls switch(es). */
  assign #1 bflyA_w = timer[0] ? (addrP_w[LOGPTS-1:1]+offsetPQ_w[LOGPTS-1:1]):
                                  addrP_w[LOGPTS-1:1];
  assign #1 fftDone_w = lastStage & timerTC;
  assign #1 swCross_w = lastStage ? 0 : timer[0];

  always @ (posedge clk) begin 
    if (clkEn) begin
      bflyA <= #1 bflyA_w;
      swCross <= #1 swCross_w;
      fftDone <= #1 fftDone_w;
    end
  end
endmodule

/******************************  TIMERS & rdValid  ****************************/
// FFT computation sequence is predefined.  Once it gets started it runs for
// a number of stages, HALFPTS+ clk per stage.  The following module sets the
// read inBuf time sequence.  Every stage takes HALFPTS + inBuf_RWDLY clk for
// the inBuf to write Bfly results back in place before it starts next stage
module rdFFTtimer (clk, cntEn, rst, nGrst, startFFT, fft_runs, timerTC, 
                   lastStage, stage, timer, rdValid);  
  parameter HALFPTS   = 128;
  parameter LOGPTS  = 8;
  parameter LOGLOGPTS  = 3;
  parameter inBuf_RWDLY = 12;

  input clk, cntEn;
  input rst, nGrst; 
  input startFFT, fft_runs;
  output timerTC, lastStage;  // terminal counts of rA and stage
  output[LOGLOGPTS-1:0] stage;
  output[LOGPTS-1:0] timer;
  output rdValid;
  reg rdValid;

  reg preRdValid, pipe1, pipe2;

  // counts pair of samples read.  There are PTS/2 pairs
  counter #(.WIDTH(LOGPTS), .TC(HALFPTS+inBuf_RWDLY-1)) rA_timer (.clk(clk),
    .nGrst(nGrst), .rst(rst|startFFT), .cntEn(cntEn), .tc(timerTC), .Q(timer) );
  // counts stages
  counter #(.WIDTH(LOGLOGPTS), .TC(LOGPTS-1)) stage_timer (.clk(clk), 
    .nGrst(nGrst),.rst(rst|startFFT),.cntEn(timerTC),.tc(lastStage),.Q(stage));
  
  always @ (posedge clk or negedge nGrst) begin
    if(!nGrst) preRdValid <= 1'b0;
      else if (rst) preRdValid <= #1 1'b0;
        else if(cntEn) begin
          if (timer==HALFPTS-1) preRdValid <= #1 1'b0;
          // on startFFT the valid reading session always starts
          if (startFFT) preRdValid <= #1 1'b1;
          // reading session starts on rTimerTC except after the lastStage 
          if ((!lastStage) & timerTC & fft_runs) preRdValid <= #1 1'b1;
        end
    end  // always  
  always @ (posedge clk) begin
    rdValid<= #1 pipe2; pipe2<= #1 pipe1; pipe1<= #1 preRdValid; //pipes  
    end  
endmodule


module wrFFTtimer(clk, cntEn, nGrst, rst, rstStage, rstTime, timerTC, lastStage,
                  stage, timer );
  parameter LOGPTS  = 8;
  parameter LOGLOGPTS  = 3;
  parameter HALFPTS = 128;

  input clk, cntEn; 
  input nGrst, rst, rstStage, rstTime;
  output timerTC, lastStage;  // terminal counts of wA and stage
  output[LOGLOGPTS-1:0] stage;
  output[LOGPTS-2:0] timer;

  // counts pair of samples read.  There are PTS/2 pairs
  counter #(.WIDTH(LOGPTS-1), .TC(HALFPTS-1)) wA_timer (.clk(clk), 
    .nGrst(nGrst),.rst(rstTime|rst),.cntEn(cntEn),.tc(timerTC), .Q(timer));
  // counts stages
  counter #(.WIDTH(LOGLOGPTS), .TC(LOGPTS-1)) stage_timer (.clk(clk), 
    .nGrst(nGrst),.rst(rstStage|rst),.cntEn(timerTC),.tc(lastStage), .Q(stage));
endmodule


/***************************  TWIDDLE rA GENERATOR  ***************************/
module twid_rA (clk, timer, stage, tA);
  parameter LOGPTS  = 8;
  parameter LOGLOGPTS  = 3;

  input clk;
  input[LOGPTS-2:0] timer;
  input[LOGLOGPTS-1:0] stage;
  output reg[LOGPTS-2:0] tA;
  //twiddleMask = ~(0xFFFFFFFF<<(NumberOfStages-1));
  //addrTwiddle =
  //    (reverseBits(count, NumberOfStages-1))<<(NumberOfStages-1-stage);
  // mask out extra left bits
  //addrTwiddle = addrTwiddle & twiddleMask;

  //reverse bits of the timer
  reg[LOGPTS-2:0] reverseBitTimer;
  wire[LOGPTS-2:0] tA_w;
  function[LOGPTS-2:0] reverse;
    input [LOGPTS-2:0] in;
    integer i;
    begin
      for (i=0; i<LOGPTS-1; i=i+1)
        reverse[i] = in[LOGPTS-2-i];
    end
  endfunction

  always @ (timer)
    reverseBitTimer = reverse(timer);

  // Left shift by
  assign #1 tA_w = reverseBitTimer<<(LOGPTS-1-stage);

  always @ (posedge clk)
    tA <= #1 tA_w;
endmodule

/***************************  TWIDDLE wA GENERATOR  ****************************
initializes Twiddle LUT on rst based on contents of twiddle.v file.  
Generates trueRst when the initialization is over */
module twid_wA (clk, ifiNreset, twid_wA, twid_wEn, rstAfterInit, twidInit);
  parameter LOGPTS  = 8;
  parameter LOGLOGPTS  = 3;

  input clk, ifiNreset;   // async global reset
  output [LOGPTS-2:0] twid_wA;
  output twid_wEn;
  output rstAfterInit, twidInit;
  reg rstAfterInit, twidInit;

  wire [LOGPTS+1:0] slowTimer_w;
  reg preRstAfterInit;
  
  // slow counter not to worry about the clk rate
  bcounter #(.WIDTH(LOGPTS+2)) slowTimer
    (.clk(clk), .nGrst(ifiNreset), .rst(1'b0), .cntEn(twidInit), .Q(slowTimer_w) );

  // wEn = 2-clk wide for the RAM to have enough time
  assign twid_wEn = (slowTimer_w[2:1]==2'b11);  
  assign twid_wA = slowTimer_w[LOGPTS+1:3];

  always @ (posedge clk or negedge ifiNreset) begin
    if (!ifiNreset)       twidInit <= #1 1'b1;
    else begin
      rstAfterInit <= #1 preRstAfterInit;
      if ( slowTimer_w == {(LOGPTS+2){1'b1}}) twidInit <= #1 1'b0;
      preRstAfterInit <= #1 ( slowTimer_w == {(LOGPTS+2){1'b1}});      
    end  
  end
endmodule


/***********************************  SM TOP  *********************************/
module sm_top (clk, clkEn, ifiStart, ifiNreset, ifiD_valid, ifiRead_y,
               ldA, rA, wA, tA, twid_wA, outBuf_wA, outBuf_rA, wEn_even, 
               wEn_odd, preSwCross, twid_wEn, inBuf_wEn, outBuf_wEn, smPong,
               ldValid, inBuf_rdValid, wLastStage, smStartFFTrd, smStartLoad, 
               ifoLoad, ifoY_valid, ifoY_rdy);  
  parameter PTS   = 256;
  parameter HALFPTS   = 128;
  parameter LOGPTS  = 8;
  parameter LOGLOGPTS  = 3;
  parameter inBuf_RWDLY = 12;

  input clk, clkEn; 
  input ifiStart, ifiNreset;  // sync and async reset
  input ifiD_valid, ifiRead_y;
  output [LOGPTS-2:0] ldA, rA, wA, tA, twid_wA; 
  output [LOGPTS-2:0] outBuf_wA;
  output [LOGPTS-1:0] outBuf_rA;
  output wEn_even, wEn_odd; 
  output preSwCross; 
  output twid_wEn;
  output inBuf_wEn, outBuf_wEn, smPong;
  output ldValid, inBuf_rdValid, wLastStage;
  output smStartFFTrd; 
  output smStartLoad; 
  output ifoLoad, ifoY_valid, ifoY_rdy;
  reg inBuf_wEn, outBuf_wEn, smPong, smStartLoad;

  wire [LOGPTS-1:0] rTimer_w;
  wire [LOGPTS-2:0] wTimer_w;
  reg [LOGPTS-2:0] timerT1;
  wire [LOGLOGPTS-1:0] rStage_w, wStage_w;
  reg [LOGLOGPTS-1:0] stageT1, stageT2;
  wire rLastStage_w, rTimerTC_w, wTimerTC_w;
  wire load_done_w, sync_rAwA, fftRd_done_w;
  reg preInBuf_wEn, preOutBuf_wEn, trueRst;
  reg smBuf_full, smFft_rdy, smFft_runs;  // top level SM registers
  /* Reset logic:
  - On ifiNreset start loading twidLUT. 
  - While it is loading keep global async rst nGrst active 
  - Once load is over issue short rstAfterInit which is just another ifiStart  */ 
  wire initRst;
  wire nGrst = ifiNreset&(!initRst);
  wire rstAfterInit, rdTimer_cntEn;
  wire trueRst_w = rstAfterInit | ifiStart;

  // Top SM outputs
  assign smStartFFTrd = smBuf_full & smFft_rdy;
  // Start loading on FFT start or initially on trueRst.  
  assign smStartLoad_w = trueRst_w | smStartFFTrd;
  // To prevent fake ifoY_rdy and ifoY_valid do not let rdFFTTimer run
  // outside smFft_runs
  assign #1 rdTimer_cntEn = clkEn & (smFft_runs | smStartFFTrd);

  // FFT read inBuf timer
  rdFFTtimer # (.HALFPTS(HALFPTS), .LOGPTS(LOGPTS),
                .LOGLOGPTS(LOGLOGPTS), .inBuf_RWDLY(inBuf_RWDLY) )
    rdFFTtimer_0 (.clk(clk), .cntEn(rdTimer_cntEn), .nGrst(nGrst), .rst(trueRst),
          .startFFT(smStartFFTrd), .timer(rTimer_w), .timerTC(rTimerTC_w),
          .stage(rStage_w), .lastStage(rLastStage_w), .fft_runs(smFft_runs),
          .rdValid(inBuf_rdValid));

  // FFT write inBuf timer
  assign sync_rAwA = (rTimer_w == inBuf_RWDLY);

  wrFFTtimer #(.LOGPTS(LOGPTS), .LOGLOGPTS(LOGLOGPTS), .HALFPTS(HALFPTS) )
    wrFFTtimer_0 (.clk(clk), .rst(trueRst), .nGrst(nGrst), 
      .rstStage(smStartFFTrd), .rstTime(sync_rAwA | smStartFFTrd), .cntEn(clkEn),
      .timer(wTimer_w), .timerTC(wTimerTC_w),
      .stage(wStage_w), .lastStage(wLastStage));

  inBuf_ldA #(.PTS(PTS), .LOGPTS(LOGPTS))
    inBuf_ldA_0 (.clk(clk), .clkEn(clkEn), .nGrst(nGrst),
                    .startLoad(smStartLoad),
                    //inData strobe
                    .ifi_dataRdy(ifiD_valid),
                    //out; inBuf is ready for new data (PTS new samples)
                    .ifo_loadOn(ifoLoad),
  //out; tells topSM the buffer is fully loaded and ready for FFTing
                    .load_done(load_done_w),
                    .ldA(ldA), .wEn_even(wEn_even), .wEn_odd(wEn_odd),
                    .ldValid(ldValid));

  inBuf_fftA #(.LOGPTS(LOGPTS), .LOGLOGPTS(LOGLOGPTS) ) inBuf_rA_0
   (.clk(clk), .clkEn(clkEn),
    .timer(rTimer_w[LOGPTS-2:0]), .stage(rStage_w),
    .timerTC(rTimerTC_w), .lastStage(rLastStage_w),
    .fftDone(fftRd_done_w), .bflyA(rA), .swCross(preSwCross));  //out

  twid_rA #(.LOGPTS(LOGPTS), .LOGLOGPTS(LOGLOGPTS) ) twid_rA_0
      (.clk(clk), .timer(timerT1), .stage(stageT2), .tA(tA));
  // Twiddle LUT initialization                  
  twid_wA #(.LOGPTS(LOGPTS), .LOGLOGPTS(LOGLOGPTS) ) twid_wA_0
      (.clk(clk), .ifiNreset(ifiNreset), 
    .twid_wA(twid_wA), .twid_wEn(twid_wEn),      // outputs 
    .twidInit(initRst), .rstAfterInit(rstAfterInit));   // outputs

  // wA generator.  On the last stage the fftRd_done comes before the last
  // FFT results get written back to the inBuf, but it is not necessary since
  // the results get written into the output buffer.
  inBuf_fftA #(.LOGPTS(LOGPTS), .LOGLOGPTS(LOGLOGPTS) )
    inBuf_wA_0 (.clk(clk), .clkEn(clkEn),
    .timer(wTimer_w), .stage(wStage_w),      //in
    .timerTC(wTimerTC_w), .lastStage(wLastStage),
    .fftDone(), .bflyA(wA), .swCross());     //out

  outBufA #(.PTS(PTS), .LOGPTS(LOGPTS) ) outBufA_0
   (.clk(clk), .clkEn(clkEn), .nGrst(nGrst), .rst(trueRst), 
    .timer(wTimer_w), .outBuf_wEn(outBuf_wEn), .rdCtl(ifiRead_y),
    .wA(outBuf_wA),.rA(outBuf_rA),.outBuf_rEn(ifoY_rdy),.rdValid(ifoY_valid));

  always @ (posedge clk) begin    // pipes
    trueRst <= #1 trueRst_w;
    smStartLoad <= #1 smStartLoad_w;
    timerT1 <= #1 rTimer_w[LOGPTS-2:0];
    stageT1 <= #1 rStage_w;
    stageT2 <= #1 stageT1;
    inBuf_wEn  <= #1 preInBuf_wEn;
    outBuf_wEn <= #1 preOutBuf_wEn;
  end  

  always @ (posedge clk or negedge nGrst) begin
    if(!nGrst) begin
      // reset topSM
      smBuf_full <= 0;
      smFft_rdy  <= 0;
      smFft_runs <= 0;
      smPong <= 1;
      preInBuf_wEn  <= 0;
      preOutBuf_wEn <= 0;
      end //nGrst
      else begin  //mark A
        if (trueRst) begin
          // reset topSM
          smBuf_full <= #1 0;
          smFft_rdy  <= #1 1;
          smFft_runs <= #1 0;
          smPong <= #1 1;
          preInBuf_wEn  <= #1 0;
          preOutBuf_wEn <= #1 0;
          end
          else begin  // mark B
            if (load_done_w) smBuf_full <= #1 1;

            if (fftRd_done_w) begin
              smFft_rdy <= #1 1;
              smFft_runs <= #1 0;
              end

            if (smStartFFTrd) begin
              smBuf_full <= #1 0;
              smFft_rdy <= #1 0;
              smFft_runs <= #1 1;
              smPong <= #1 ~smPong;
              end

            if(sync_rAwA)
              if(rLastStage_w)  preOutBuf_wEn <= #1 1;
              else if(smFft_runs)  preInBuf_wEn <= #1 1;

            if(wTimerTC_w) begin
              preInBuf_wEn <= #1 0;
              preOutBuf_wEn <= #1 0;
            end
        end  // mark B
    end // mark A
  end  // always
endmodule
