--
--	fft2-base.occ -- FFT stream benchmark in occam-pi
--	Copyright (C) 2009  Carl Ritson
--
--      Adapted from the StreamIT FFT2 benchmark.
--
--	This program is free software; you can redistribute it and/or modify
--	it under the terms of the GNU General Public License as published by
--	the Free Software Foundation; either version 2 of the License, or
--	(at your option) any later version.
--
--	This program is distributed in the hope that it will be useful,
--	but WITHOUT ANY WARRANTY; without even the implied warranty of
--	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--	GNU General Public License for more details.
--
--	You should have received a copy of the GNU General Public License
--	along with this program; if not, write to the Free Software
--	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
--

#INCLUDE "course.module"
#INCLUDE "maths.module"

-- Constants
VAL []INT L2.SEQ    IS [ 1, 2, 4, 8, 16, 32, 64, 128, 256 ]:
VAL INT L2.FFT.MAX  IS 8:
VAL INT FFT.MAX     IS 128:

INT FUNCTION log2 (VAL INT n)
  INT count:
  VALOF
    INT val:
    SEQ
      count := 0
      val   := n
      WHILE (((val \ 2) = 0)) AND (val > 1)
        count, val := count + 1, val / 2
    RESULT count
:

PROC combine.dft (VAL INT n, CHAN MOBILE []REAL32 in?, out!)
  VAL REAL32 wn.r IS COS (( 2.0) * (3.141592654 / (REAL32 TRUNC n))):
  VAL REAL32 wn.i IS SIN ((-2.0) * (3.141592654 / (REAL32 TRUNC n))):

  MOBILE []REAL32 w:
  
  SEQ
    w := MOBILE [n]REAL32

    INITIAL REAL32 real IS 1.0:
    INITIAL REAL32 imag IS 0.0:
    SEQ i = 0 FOR (n / 2) STEP 2
      SEQ
        w[i]    := real
        w[i+1]  := imag
        real, imag := (real * wn.r) - (imag * wn.i), (real * wn.i) + (imag * wn.r)

    INITIAL BOOL running IS TRUE:
    WHILE running
      MOBILE []REAL32 data, result:
      SEQ
        in ? data
        result := MOBILE [SIZE data]REAL32
        IF
          (SIZE data) = 0
            running := FALSE
          TRUE
            SEQ offset = 0 FOR ((SIZE data) / (2 * n)) STEP (2 * n)
              SEQ
                VAL []REAL32  data    IS [ data   FROM offset FOR (2 * n) ]:
                []REAL32      result  IS [ result FROM offset FOR (2 * n) ]:
                SEQ i = 0 FOR (n / 2) STEP 2
                  VAL INT     i.plus.1    IS i + 1:
                  VAL REAL32  y0.r        IS data[i]:
                  VAL REAL32  y0.i        IS data[i.plus.1]:
                  VAL REAL32  y1.r        IS data[n + i]:
                  VAL REAL32  y1.i        IS data[n + i.plus.1]:
                  VAL REAL32  weight.real IS w[i]:
                  VAL REAL32  weight.imag IS w[i.plus.1]:
                  VAL REAL32  y1w.r       IS (y1.r * weight.real) - (y1.i * weight.imag):
                  VAL REAL32  y1w.i       IS (y1.r * weight.imag) + (y1.i * weight.real):
                  SEQ
                    result[i]           := y0.r + y1w.r
                    result[i.plus.1]    := y0.i + y1w.i
                    result[n+i]         := y0.r - y1w.r
                    result[n+i.plus.1]  := y0.i - y1w.i
                #IF DEFINED (USE.RESCHEDULE)
                RESCHEDULE ()
                #ENDIF
        out ! result
:

PROC fft.reorder.simple (VAL INT n, CHAN MOBILE []REAL32 in?, out!)
  INITIAL BOOL running IS TRUE:
  WHILE running
    MOBILE []REAL32 data, result:
    SEQ
      in ? data
      result := MOBILE [SIZE data]REAL32
      IF
        (SIZE data) = 0
          running := FALSE
        TRUE 
          SEQ offset = 0 FOR ((SIZE data) / (2 * n)) STEP (2 * n)
            VAL []REAL32  data    IS [ data   FROM offset FOR (2 * n) ]:
            []REAL32      result  IS [ result FROM offset FOR (2 * n) ]:
            INITIAL INT pos IS 0:
            SEQ
              SEQ i = 0 FOR ((SIZE data) / 4) STEP 4
                SEQ
                  result[pos]   := data[i]
                  result[pos+1] := data[i+1]
                  pos           := pos + 2
              SEQ i = 2 FOR ((SIZE data) / 4) STEP 4
                SEQ
                  result[pos]   := data[i]
                  result[pos+1] := data[i+1]
                  pos           := pos + 2
              #IF DEFINED (USE.RESCHEDULE)
              RESCHEDULE ()
              #ENDIF
      out ! result
:

PROC fft.reorder (VAL INT n, CHAN MOBILE []REAL32 in?, out!)
  VAL INT log2.n IS log2 (n):
  [L2.FFT.MAX]CHAN MOBILE []REAL32 pipe:
  PAR j = 0 FOR (log2.n - 1)
    VAL INT i IS L2.SEQ[j]:
    IF
      j = 0
        fft.reorder.simple (n / i, in?, pipe[0])
      j < (log2.n - 2)
        fft.reorder.simple (n / i, pipe[j-1]?, pipe[j])
      TRUE
        fft.reorder.simple (n / i, pipe[j-1]?, out!)
:

PROC fft.kernel.2 (VAL INT n, CHAN MOBILE []REAL32 in?, out!)
  VAL INT log2.n  IS log2 (n):
  VAL INT split   IS SPLIT.ELEMENTS:
  
  [split]CHAN MOBILE []REAL32 p.in, p.out:
  
  PAR
    INITIAL INT   active  IS 0:
    INITIAL BOOL  running IS TRUE:
    SEQ
      WHILE running
        MOBILE []REAL32 data:
        SEQ
          in ? data
          IF
            (SIZE data) = 0
              running := FALSE
            TRUE
              p.in[active] ! data
          active := (active + 1) \ split
      PAR i = 0 FOR split
        p.in[i] ! MOBILE [0]REAL32

    INITIAL INT   active  IS 0:
    INITIAL INT   running IS split:
    SEQ
      WHILE running > 0
        MOBILE []REAL32 data:
        SEQ
          p.out[active] ? data
          IF
            (SIZE data) = 0
              running := running - 1
            TRUE
              out ! data
          active := (active + 1) \ split
      out ! MOBILE [0]REAL32

    PAR i = 0 FOR split
      [L2.FFT.MAX]CHAN MOBILE []REAL32 trans:
      PAR
        fft.reorder (n, p.in[i], trans[0])
        PAR j = 0 FOR log2.n
          VAL INT n IS L2.SEQ[j + 1]:
          IF
            j < (log2.n - 1)
              combine.dft (n, trans[j], trans[j+1])
            TRUE
              combine.dft (n, trans[j], p.out[i])
:

PROC test.source (VAL INT count, n, CHAN MOBILE []REAL32 out!)
  SEQ
    SEQ i = 0 FOR count
      MOBILE []REAL32 data:
      SEQ
        data := MOBILE [2 * n]REAL32
        SEQ i = 0 FOR SIZE data
          data[i] := 0.0
        data[2] := 1.0
        out ! data
    out ! MOBILE [0]REAL32
:

PROC sink (CHAN MOBILE []REAL32 in?, CHAN BYTE scr!)
  INITIAL BOOL running IS TRUE:
  WHILE running
    MOBILE []REAL32 data:
    SEQ
      in ? data
      running := (SIZE data) > 0
:

PROC main (CHAN BYTE kyb?, scr!, err!)
  CHAN MOBILE []REAL32 data, result:
  VAL INT n IS FFT.SIZE:
  PAR
    test.source (CYCLES, n, data!)
    fft.kernel.2 (n, data?, result!)
    sink (result?, scr!)
:
