Документ взят из кэша поисковой машины. Адрес оригинального документа : http://www.naic.edu/~phil/hardware/pdev/fpga/gx/jfft/src/mkjfft_twiddle
Дата изменения: Thu Jun 26 04:28:48 2008
Дата индексирования: Sat Sep 6 20:13:08 2008
Кодировка:

Поисковые слова: m 63
#!/usr/bin/perl

#
# Jeff Mock
# 2030 Gough
# San Francisco, CA 94109
# jeff@mock.com
# (c) 2004
#

#
# $URL: https://www.mock.com/svn/pdev/trunk/gx/jfft/src/mkjfft_twiddle $
# $Id: mkjfft_twiddle 900 2007-02-18 19:46:19Z jeff $
#

use Getopt::Long;
use Math::Trig;
use Math::BigInt;
use POSIX;

$opt_width = 18; # datapath width
$opt_n = 32; # length of fft
$opt_twiddle = "";
$opt_dir = ".";
$opt_odir = ".";
$opt_prefix = "jfft";
$opt_norun = 0;
$opt_imp = "virtex2";
$opt_cm3 = 0;
$opt_ce = 0;

$0 =~ /(.*)\/.*/;
$opt_dir = $1 eq "" ? "." : $1;

# binmode(STDIN, ":bytes") if $] >= 5.008;

%opts = (
'width=o' => \$opt_width,
'n=o' => \$opt_n,
'twiddle=s' => \$opt_twiddle,
'dir=s' => \$opt_dir,
'odir=s' => \$opt_odir,
'prefix=s' => \$opt_prefix,
'norun' => \$opt_norun,
'imp=s' => \$opt_imp,
'cm3' => \$opt_cm3,
'ce' => \$opt_ce,
);

if (!GetOptions(%opts)) {
print STDERR "
Generate pieces of verilog for FFTs

mkjfft_twiddle [options]
[--n=n] Length of FFT ($opt_n)
[--width=n] Datapath width of FFT ($opt_width)
[--twiddle=n,m] Generate twiddle table with n values, inc by m
[--dir=s] Directory with other mkjfft programs ($opt_dir)
[--odir=s] Output directory for verilog ($opt_odir)
[--prefix=s] Prefix module name with string ($opt_prefix)
[--norun] Do not recurse and build sub-modules
[--imp=s] Set target implementation ($opt_imp)
[--ce] Add clock enable

\n";
exit 1;
}

sub pcode {
my $fd = shift;
my $sp = shift;
my $code = shift;

$code =~ s/^.*?\n//m;
$code =~ s/^ {$sp}//mg;
$code =~ s/ *$//;
print $fd $code;
}

sub log2 {
my $v = shift;
return int(log(2*$v-1)/log(2));
}

sub twiddle_vals {
my $tw_n = shift;
my $tw_inc = shift;

my @rehex = ();
my @rereal = ();
my @imhex = ();
my @imreal = ();

$ln = 0.0;
for my $i (0 .. $tw_n-1) {
$theta = 2.0 * pi * $ln / $opt_n;
$re = cos($theta);
$im = (-1.0) * sin($theta);

# $reh = int($re * (1<< ($opt_width-2))) & ((1 << $opt_width)-1);
# $imh = int($im * (1<< ($opt_width-2))) & ((1 << $opt_width)-1);
$reh = int($re * (1<< ($opt_width-1)));
$imh = int($im * (1<< ($opt_width-1)));
$reh = (1 << ($opt_width-1))-1 if $reh >= (1 << ($opt_width-1));
$imh = (1 << ($opt_width-1))-1 if $imh >= (1 << ($opt_width-1));
$reh = -(1 << ($opt_width-1)) if $reh < -(1 << ($opt_width-1));
$imh = -(1 << ($opt_width-1)) if $imh < -(1 << ($opt_width-1));
$reh = $reh & ((1 << $opt_width)-1);
$imh = $imh & ((1 << $opt_width)-1);

push @rehex, $reh;
push @imhex, $imh;
push @rereal, $re;
push @imreal, $im;

$ln += $tw_inc;
}
return (\@rehex, \@imhex, \@rereal, \@imreal);
}

our $inst_num=0;

sub virtex2_bram {
my $fd = shift;
my $ports = shift;
my $addr = shift;
my $data = shift;
my $awidth = shift;
my $dwidth = shift;
my $vals = shift;
my $cmts = shift;
my $addrb = shift;
my $datab = shift;

my $n = @$vals;
my $dwidm1 = $dwidth-1;

my %parts = (
# depth => bram address bits :
# main data width of bram :
# parity data width of bram :
# name of single port part :
# name of dual port part
8 => "9:32:4:RAMB16_S36:RAMB16_S36_S36",
16 => "9:32:4:RAMB16_S36:RAMB16_S36_S36",
32 => "9:32:4:RAMB16_S36:RAMB16_S36_S36",
64 => "9:32:4:RAMB16_S36:RAMB16_S36_S36",
128 => "9:32:4:RAMB16_S36:RAMB16_S36_S36",
256 => "9:32:4:RAMB16_S36:RAMB16_S36_S36",
512 => "9:32:4:RAMB16_S36:RAMB16_S36_S36",
1024 => "10:16:2:RAMB16_S18:RAMB16_S18_S18",
2048 => "11:8:1:RAMB16_S9:RAMB16_S9_S9",
4096 => "12:4:0:RAMB16_S4:RAMB16_S4_S4",
8192 => "13:2:0:RAMB16_S2:RAMB16_S2_S2",
16384 => "14:1:0:RAMB16_S1:RAMB16_S1_S1",
);

die "twiddle ROM depth ($n) is too deep for Xilinx"
if $n > 16384;
die "twiddle ROM depth ($n) is not power of 2?"
if 1 << log2($n) != $n;
die "twiddle ROM size ($n) is too small for BRAM!"
if ($n < 8);
die "twiddle ROM width ($dwidth) is too small!"
if ($dwidth < 1);

my ($braddr, $mwid, $pwid, $brname, $brnameb) = split ':', $parts{$n};
$brname = $brnameb if $ports==2;

my $nparts = ceil($dwidth/($mwid+$pwid));

## print "awidth = $awidth\n";
## print "dwidth = $dwidth\n";
## print "n = $n\n";
## print "braddr = $braddr\n";
## print "mwid = $mwid\n";
## print "pwid = $pwid\n";
## print "nparts = $nparts\n";

# Make two tables for the width in each memory to use in
# the main part and the parity part. Use the main parts
# first and then go back and use parity parts. The bit
# assignments are different though, first bit range is
# main part of first memory, second section is parity of
# first memory, and so on.
#
my @muwid = ();
my @puwid = ();
my $bits = $dwidth;
for my $npart (0 .. $nparts-1) {
$muwid[$npart] = $bits;
$muwid[$npart] = $mwid if $mwid < $bits;
$bits -= $muwid[$npart];
}
for my $npart (0 .. $nparts-1) {
$puwid[$npart] = $bits;
$puwid[$npart] = $pwid if $pwid < $bits;
$bits -= $puwid[$npart];
}
die "Botch: $bits bits left after width assignment"
if $bits > 0;

# Declare wires for memory output
#
pcode($fd, 4, "
wire [${dwidm1}:0] ${data};
");
pcode($fd, 4, "
wire [${dwidm1}:0] ${datab};
") if ($ports==2);

# Iterate through the blockrams and initialize them and then
# instantiate them.
#
my $bitpos = 0;
for my $npart (0 .. $nparts-1) {
my $bname = sprintf "m%02d", $inst_num++;

# Make a mask for bits from this BRAM
#
my $mask = Math::BigInt->new(1);
$mask->blsft($muwid[$npart]);
$mask->bdec();

# Iterate though the (max) 64 parameters used to init
# blockram. Each parameter initializes 256-bits of the ram
# (regardless of the ram configuration). The bits are
# organized in little-endian fashion.
#
my $tidx = 0;
my @init_m = ();
my @init_p = ();
printf $fd "\n%s// synthesis translate_off\n", " "x4;
printf $fd "%sdefparam\n", " "x4;
for my $init (0 .. 63) {
my $iv = Math::BigInt->new(0);
for my $pos (0 .. 256/$mwid-1) {
my $tv = $$vals[$tidx]->copy;
$tv->brsft($bitpos);
$tv->band($mask);
$tv->blsft($pos*$mwid);
$iv->bior($tv);
$tidx++;
last if $tidx >= $n;
}
my $str = $iv->as_hex;
$str =~ s/^0x//;
$str = ("0"x(64-length($str))) . $str if length($str) < 64;
printf $fd "%s%s.INIT_%02X = 256\'h%s%s\n", " "x8, $bname,
$init, $str, $tidx>=$n ? ";" : ",";
$init_m[$init] = $str;
last if $tidx >= $n;
}
$mbitpos = $bitpos;
$bitpos += $muwid[$npart];

# Do the same iteration except through the (max) 8
# parameters used to initialize the extra parity bits of
# the ram.
#
if ($puwid[$npart] > 0) {
my $mask = Math::BigInt->new(1);
$mask->blsft($puwid[$npart]);
$mask->bdec();

my $tidx = 0;
printf $fd "\n%sdefparam\n", " "x4;
for my $init (0 .. 7) {
my $iv = Math::BigInt->new(0);
for my $pos (0 .. 256/$pwid-1) {
my $tv = $$vals[$tidx]->copy;
$tv->brsft($bitpos);
$tv->band($mask);
$tv->blsft($pos*$pwid);
$iv->bior($tv);
$tidx++;
last if $tidx >= $n;
}
my $str = $iv->as_hex;
$str =~ s/^0x//;
$str = ("0"x(64-length($str))).$str if length($str) < 64;
printf $fd "%s%s.INITP_%02X = 256'h%s%s\n", " "x8, $bname,
$init, $str, $tidx>=$n ? ";" : ",";
$init_p[$init] = $str;
last if $tidx >= $n;
}
$pbitpos = $bitpos;
$bitpos += $puwid[$npart];
}
printf $fd "%s// synthesis translate_on\n\n", " "x4;

# Initialize main part for synth
# reuse init strings saved from defparams
#
for my $init (0..63) {
if (defined($init_m[$init])) {
printf $fd "%s// synthesis attribute INIT_%02X of %s is \"%s\"\n",
" "x4, $init, $bname, $init_m[$init];
}
}

# Initialize parity part for synth
if ($puwid[$npart] > 0) {
for my $init (0..7) {
if (defined($init_p[$init])) {
printf $fd "%s// synthesis attribute INITP_%02X of %s is \"%s\"\n",
" "x4, $init, $bname, $init_p[$init];
}
}
i }


# Make instance of the BRAM. Complicated because it
# handles both single and dual ported cases.
#
# Also handles awkward cases where full data width isn't
# used and dummy wires need to be declared to make sure
# the right portion of a partially used RAM connects
# correctly. bleh.
#
my $ram_addr = $addr;
$ram_addr = sprintf("{ %d'b0, %s}", $braddr-$awidth, $addr)
if $braddr > $awidth;

my $ram_do = sprintf("%s[%d:%d]", $data, $mbitpos+$muwid[$npart]-1,
$mbitpos);
if ($mwid > $muwid[$npart]) {
my $ew = $mwid - $muwid[$npart]-1;
pcode($fd, 16, "
wire [${ew}:0] dnc_${bname};
");
$ram_do = "{dnc_${bname}, ${ram_do}}";
}
my $ram_pdo = "";
if ($pwid > 0) {
if ($puwid[$npart] > 0) {
$ram_pdo = sprintf("%s[%d:%d]", $data,
$pbitpos+$puwid[$npart]-1, $pbitpos);
if ($pwid > $puwid[$nparp]) {
my $ew = $pwid - $puwid[$npart]-1;
pcode($fd, 24, "
wire [${ew}:0] pnc_${bname};
");
$ram_pdo = "{pnc_${bname}, ${ram_pdo}}";
}
} else {
my $ew = $pwid-1;
pcode($fd, 20, "
wire [${ew}:0] pnc_${bname};
");
$ram_pdo = "pnc_${bname}";
}
}

if ($ports == 2) {
my $ram_addrb = $addrb;
$ram_addrb = sprintf("{ %d'b0, %s}", $braddr-$awidth, $addrb)
if $braddr > $awidth;

my $ram_dob = sprintf("%s[%d:%d]", $datab,
$mbitpos+$muwid[$npart]-1, $mbitpos);

if ($mwid > $muwid[$npart]) {
my $ew = $mwid - $muwid[$npart]-1;
pcode($fd, 20, "
wire [${ew}:0] dbnc_${bname};
");
$ram_dob = "{dbnc_${bname}, ${ram_dob}}";
}

if ($pwid > 0) {
if ($puwid[$npart] > 0) {
$ram_pdob = sprintf("%s[%d:%d]", $datab,
$pbitpos+$puwid[$npart]-1, $pbitpos);
if ($pwid > $puwid[$nparp]) {
my $ew = $pwid - $puwid[$npart]-1;
pcode($fd, 28, "
wire [${ew}:0] pbnc_${bname};
");
$ram_pdob = "{pbnc_${bname}, ${ram_pdob}}";
}
} else {
my $ew = $pwid-1;
pcode($fd, 24, "
wire [${ew}:0] pbnc_${bname};
");
$ram_pdob = "pbnc_${bname}";
}
}

pcode($fd, 12, "
${brname} ${bname} (
.CLKA ( ck ),
.CLKB ( ck ),
.ADDRA ( ${ram_addr} ),
.ADDRB ( ${ram_addrb} ),

.DIA ( ${mwid}'b0 ),
.DIB ( ${mwid}'b0 ),
.DOA ( ${ram_do} ),
.DOB ( ${ram_dob} ),
");
pcode($fd, 12, "
.DIPA ( ${pwid}'b0 ),
.DIPB ( ${pwid}'b0 ),
.DOPA ( ${ram_pdo} ),
.DOPB ( ${ram_pdob} ),
") if $pwid>0;
pcode($fd, 12, "
.ENA ( 1'b1 ),
.WEA ( 1'b0 ),
.SSRA ( 1'b0 ),
.ENB ( 1'b1 ),
.WEB ( 1'b0 ),
.SSRB ( 1'b0 )
);
") if !$opt_ce;
pcode($fd, 12, "
.ENA ( ce ),
.WEA ( 1'b0 ),
.SSRA ( 1'b0 ),
.ENB ( ce ),
.WEB ( 1'b0 ),
.SSRB ( 1'b0 )
);
") if $opt_ce;
} else {
pcode($fd, 12, "
${brname} ${bname} (
.CLK ( ck ),
.ADDR ( ${ram_addr} ),

.DI ( ${mwid}'b0 ),
.DO ( ${ram_do} ),
");
pcode($fd, 12, "
.DIP ( ${pwid}'b0 ),
.DOP ( ${ram_pdo} ),
") if $pwid>0;
pcode($fd, 12, "
.EN ( ce ),
.WE ( 1'b0 ),
.SSR ( 1'b0 )
);
") if $opt_ce;
pcode($fd, 12, "
.EN ( 1'b1 ),
.WE ( 1'b0 ),
.SSR ( 1'b0 )
);
") if !$opt_ce;
}
}
}

sub twiddle_rom {
my $fd = shift;
my $addr = shift;
my $data = shift;
my $awidth = shift;
my $dwidth = shift;
my $vals = shift;
my $cmts = shift;
my $n = @$vals;
my $dwidm1 = $dwidth-1;

if ($opt_imp eq "virtex2" && $n > 32) {
virtex2_bram($fd, 1, $addr, $data, $awidth, $dwidth, $vals, $cmts);
} else {
# behavioral implementation, also synthesizes well for xilinx
# if rom width is power of 2, but sadly XST synth is inefficient
# if you want a dual port rom or width is something like 9, 18,
# or 36. For these cases primitives need to be instantiated.
#
my $type = "DISTRIBUTED";
my $attr = "";
$type = "BLOCK" if $n>32;
$attr = "synthesis attribute ROM_STYLE of ${data} is ${type}"
if ($opt_imp eq "virtex2");
pcode($fd, 12, "

// ${attr}
reg [${dwidm1}:0] ${data};
");
pcode($fd, 12, "
always @(posedge ck) begin
") if !$opt_ce;
pcode($fd, 12, "
always @(posedge ck) if (ce) begin
") if $opt_ce;
pcode($fd, 12, "
case (${addr})
");
for my $i (0 .. $n-1) {
my $bre = Math::BigInt->new($$reref[$i]);
my $bim = Math::BigInt->new($$imref[$i]);
$bim->blsft($opt_width);
$bim->bior($bre);

my $str = $$vals[$i]->as_hex();
$str =~ s/^0x//;

printf $fd "%s// %s\n", " "x12, $$cmts[$i];
printf $fd "%s${awidth}\'d%d : ${data} <= ${dwidth}\'h${str};\n",
" " x 12, $i;
}
pcode($fd, 12, "
endcase
end
");
}
}

sub twiddle_dp_rom {
my $fd = shift;
my $addra = shift;
my $addrb = shift;
my $dataa = shift;
my $datab = shift;
my $awidth = shift;
my $dwidth = shift;
my $vals = shift;
my $cmts = shift;

my $n = @$vals;
if ($opt_imp eq "virtex2" && $n > 32) {
virtex2_bram($fd, 2, $addra, $dataa, $awidth, $dwidth, $vals, $cmts,
$addrb, $datab);
} else {
twiddle_rom($fd, $addra, $dataa, $awidth, $dwidth, $vals, $cmts);
twiddle_rom($fd, $addrb, $datab, $awidth, $dwidth, $vals, $cmts);
}
}

sub twiddle_module {
my $fd;
my ($tw_n, $tw_inc) = split ',', $opt_twiddle;

my $tad = log2($tw_n);
my $tadm1 = $tad - 1;
my $tadm2 = $tad - 2;
my $wid = $opt_width;
my $widm1 = $wid - 1;

my ($reref, $imref, $rer, $imr) = twiddle_vals($tw_n, $tw_inc);
my $fn = "${opt_odir}/${opt_prefix}_twiddle_${opt_n}_${tw_n}_${tw_inc}.v";
if (-s $fn) {
print " $fn exists, mkjfft_twiddle not creating\n";
return 0;
}
print " Creating $fn\n";
open $fd, "> $fn" or die "mkjfft_twiddle cannot create file $fn.\n $!";

# Try to figure out which method to apply for generating
# twiddle table.
#
# By default use method3 where there is a single dual-port
# ROM table from 0-pi/2 with extra logic to generate proper
# result. These are pretty much biased towards virtex2
# with 16k dual port rams that can be used as ROMs.
#
my $typ = "method3";

# For tables with 512 or less entries do simple method with
# two tables. This winds up in a single virtex-2 BRAM for
# widths of 18-bits or less.
#
$typ = "method1" if $tw_n <= 512;
$typ = "method1" if $tw_n <= 1024 && $opt_width <= 9;

# Use intermediate method 2 with a single table from 0-pi and
# simpler result munging to generate twiddle factors.
#
$typ = "method2" if ($tw_n==1024 && $opt_width <= 18 && $opt_width > 9);
$typ = "method2" if ($tw_n==2048 && $opt_width <= 9);

pcode($fd, 8, "
// Twiddle factor table for FFT
//
// FFT size is ${opt_n}
// ${tw_n} twiddle factors
//
// 3 pipeline delays from addr to output
//
// Generated by mkjfft_twiddle
// Using ${typ}
//
module ${opt_prefix}_twiddle_${opt_n}_${tw_n}_${tw_inc} (
ck,
");
pcode($fd, 8, "
ce,
") if $opt_ce;
pcode($fd, 8, "
addr,
tw_re,
tw_im
);

input ck;
");
pcode($fd, 8, "
input ce;
") if $opt_ce;
pcode($fd, 8, "
input [${tadm1}:0] addr;
output [${widm1}:0] tw_re;
output [${widm1}:0] tw_im;
");

if ($typ eq "method3") {
# Single dual port table of -sin from 0-pi/2.
# Fiddle logic to generate both real part (cos)
# and imaginary pat (-sin). This is for large
# twiddle tables.

my $mem_width = $opt_width;

# Make "1" (really 0.9999...) in memory width, verilog syntax
#
my $tone = Math::BigInt->new(1);
$tone->blsft($widm1);
$tone->bdec();
my $one = $tone->as_hex();
$one =~ s/^0x//;
$one = "${wid}'h${one}";

# Make "-1" in memory width, verilog syntax
#
my $tmone = Math::BigInt->new(1);
$tmone->blsft($widm1);
my $mone = $tmone->as_hex();
$mone =~ s/^0x//;
$mone = "${wid}'h${mone}";

pcode($fd, 12, "

reg [${tadm2}:0] addr_re, addr_im;
reg addr_z1, addr_z;
reg addr_n1, addr_n;
wire [${tadm2}:0] addrm;

reg [${widm1}:0] tw_re;
reg [${widm1}:0] tw_im;

assign addrm = -addr[${tadm2}:0];
");
pcode($fd, 12, "
always @(posedge ck) begin
") if !$opt_ce;
pcode($fd, 12, "
always @(posedge ck) if (ce) begin
") if $opt_ce;
pcode($fd, 12, "
addr_re <= addr[${tadm1}] ? addr[${tadm2}:0] : addrm;
addr_im <= addr[${tadm1}] ? addrm : addr[${tadm2}:0];
addr_z1 <= (addr[${tadm2}:0] == ${tadm1}'h0);
addr_z <= addr_z1;
addr_n1 <= addr[${tadm1}];
addr_n <= addr_n1;
end
");

my @vals = ();
my @cmts = ();
for my $i (0 .. $tw_n/2-1) {
$vals[$i] = Math::BigInt->new($$imref[$i]);
$cmts[$i] = sprintf "Im(W%d) = %+.3fj", $i*$tw_inc, $$imr[$i];
}
twiddle_dp_rom( $fd, "addr_re", "addr_im", "memout_re", "memout_im",
$tadm1, $mem_width, \@vals, \@cmts);


pcode($fd, 12, "
always @(posedge ck) begin
") if !$opt_ce;
pcode($fd, 12, "
always @(posedge ck) if (ce) begin
") if $opt_ce;
pcode($fd, 12, "
tw_im <= (addr_n & addr_z) ? ${mone} : memout_im;
tw_re <= (addr_z & ~addr_n) ? ${one} :
(addr_n ? memout_re : -memout_re);
end

");
}

if ($typ eq "method2") {
# Single dual port table of -sin from 0-pi. No
# logic for imaginary part (-sin), simple logic for
# real part (cos).
#

my $mem_width = $opt_width;

# Make "1" (really 0.9999) in memory width, verilog syntax
#
my $tone = Math::BigInt->new(1);
$tone->blsft($widm1);
$tone->bdec();
my $one = $tone->as_hex();
$one =~ s/^0x//;
$one = "${wid}'h${one}";

pcode($fd, 12, "

reg [${tadm1}:0] addr_re, addr_im;
reg addr_z1, addr_z;
reg addr_n1, addr_n;

reg [${widm1}:0] tw_re;
reg [${widm1}:0] tw_im;

");
pcode($fd, 12, "
always @(posedge ck) begin
") if !$opt_ce;
pcode($fd, 12, "
always @(posedge ck) if (ce) begin
") if $opt_ce;
pcode($fd, 12, "
addr_re <= { ~addr[${tadm1}], addr[${tadm2}:0] };
addr_z1 <= (addr == ${tad}'h0);
addr_z <= addr_z1;
addr_n1 <= addr[${tadm1}];
addr_n <= addr_n1;
addr_im <= addr;
end
");

my @vals = ();
my @cmts = ();
for my $i (0 .. $tw_n-1) {
$vals[$i] = Math::BigInt->new($$imref[$i]);
$cmts[$i] = sprintf "Im(W%d) = %+.3fj", $i*$tw_inc, $$imr[$i];
}
twiddle_dp_rom( $fd, "addr_re", "addr_im", "memout_re", "memout_im",
$tad, $mem_width, \@vals, \@cmts);

pcode($fd, 12, "
always @(posedge ck) begin
") if !$opt_ce;
pcode($fd, 12, "
always @(posedge ck) if (ce) begin
") if $opt_ce;
pcode($fd, 12, "
tw_im <= memout_im;
tw_re <= addr_z ? ${one} :
(addr_n ? memout_re : -memout_re);
end

");
}

if ($typ eq "method1") {
# Separate sin/cos tables, 0-pi, no external logic,
# simplest solution for small tables
#

my $wid2m1 = 2*$wid - 1;
my $mem_width = 2*$opt_width;

pcode($fd, 8, "

reg [${tadm1}:0] addr2;
reg [${widm1}:0] tw_re;
reg [${widm1}:0] tw_im;
");

pcode($fd, 8, "
always @(posedge ck) begin
") if !$opt_ce;
pcode($fd, 8, "
always @(posedge ck) if (ce) begin
") if $opt_ce;
pcode($fd, 8, "
addr2 <= addr;
end
");
my @vals = ();
my @cmts = ();
for my $i (0 .. $tw_n-1) {
my $bre = Math::BigInt->new($$reref[$i]);
my $bim = Math::BigInt->new($$imref[$i]);
$bim->blsft($opt_width);
$bim->bior($bre);
$vals[$i] = $bim;
$cmts[$i] = sprintf "W%d = %.3f%+.3fj",
$i*$tw_inc, $$rer[$i], $$imr[$i];
}
twiddle_rom( $fd, "addr2", "memout", $tad,
$mem_width, \@vals, \@cmts);

pcode($fd, 12, "
always @(posedge ck) begin
") if !$opt_ce;
pcode($fd, 12, "
always @(posedge ck) if (ce) begin
") if $opt_ce;
pcode($fd, 12, "
tw_re <= memout[${widm1}:0];
tw_im <= memout[${wid2m1}:${opt_width}];
end
");
}

pcode($fd, 8, "
endmodule
");
close $fd;
}

twiddle_module() if $opt_twiddle;