text: //
  //  Copyright (C) 2015  Clifford Wolf <clifford@clifford.at>
  //
  //  Permission to use, copy, modify, and/or distribute this software for any
  //  purpose with or without fee is hereby granted, provided that the above
  //  copyright notice and this permission notice appear in all copies.
  //
  //  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  //  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  //  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  //  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  //  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  //  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  //  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  //
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <string.h>
  #include <math.h>
  
  #ifdef __EMSCRIPTEN__
  #include <emscripten.h>
  #endif
  
  const char *binstr(int v, int n)
  {
  	static char buffer[16];
  	char *p = buffer;
  
  	for (int i = n-1; i >= 0; i--)
  		*(p++) = ((v >> i) & 1) ? '1' : '0';
  	*(p++) = 0;
  
  	return buffer;
  }
  
  void help(const char *cmd)
  {
  	printf("\n");
  	printf("Usage: %s [options]\n", cmd);
  	printf("\n");
  	printf("    -i <input_freq_mhz>\n");
  	printf("        PLL Input Frequency (default: 12 MHz)\n");
  	printf("\n");
  	printf("    -o <output_freq_mhz>\n");
  	printf("        PLL Output Frequency (default: 60 MHz)\n");
  	printf("\n");
  	printf("    -S\n");
  	printf("        Disable SIMPLE feedback path mode\n");
  	printf("\n");
  	printf("    -f <filename>\n");
  	printf("        Save PLL configuration as Verilog to file\n");
  	printf("\n");
  	printf("    -m\n");
  	printf("        Save PLL configuration as Verilog module (use with -f)\n");
  	printf("\n");
  	printf("    -n <module name>\n");
  	printf("        Specify different Verilog module name than the default 'pll'\n");
  	printf("\n");
  	printf("    -q\n");
  	printf("        Do not print PLL configuration to stdout\n");
  	printf("\n");
  	exit(1);
  }
  
  int main(int argc, char **argv)
  {
  #ifdef __EMSCRIPTEN__
  	EM_ASM(
  		if (ENVIRONMENT_IS_NODE)
  		{
  			FS.mkdir('/hostcwd');
  			FS.mount(NODEFS, { root: '.' }, '/hostcwd');
  			FS.mkdir('/hostfs');
  			FS.mount(NODEFS, { root: '/' }, '/hostfs');
  		}
  	);
  #endif
  
  	double f_pllin = 12;
  	double f_pllout = 60;
  	bool simple_feedback = true;
  	char* filename = NULL;
  	char* module_name = NULL;
  	bool save_as_module = false;
  	bool quiet = false;
  
  	int opt;
  	while ((opt = getopt(argc, argv, "i:o:Smf:n:q")) != -1)
  	{
  		switch (opt)
  		{
  		case 'i':
  			f_pllin = atof(optarg);
  			break;
  		case 'o':
  			f_pllout = atof(optarg);
  			break;
  		case 'S':
  			simple_feedback = false;
  			break;
  		case 'm':
  			save_as_module = true;
  			break;
  		case 'f':
  			filename = optarg;
  			break;
  		case 'n':
  			module_name = optarg;
  			break;
  		case 'q':
  			quiet = true;
  			break;
  		default:
  			help(argv[0]);
  		}
  	}
  
  	if (optind != argc)
  		help(argv[0]);
  
  	// error: shall save as module, but no filename was given
  	if (save_as_module && filename == NULL)
  		help(argv[0]);
  
  	bool found_something = false;
  	double best_fout = 0;
  	int best_divr = 0;
  	int best_divf = 0;
  	int best_divq = 0;
  
  	// The documentation in the iCE40 PLL Usage Guide incorrectly lists the
  	// maximum value of DIVF as 63, when it is only limited to 63 when using
  	// feedback modes other that SIMPLE.
  	int divf_max = simple_feedback ? 127 : 63;
  
  	if (f_pllin < 10 || f_pllin > 133) {
  		fprintf(stderr, "Error: PLL input frequency %.3f MHz is outside range 10 MHz - 133 MHz!\n", f_pllin);
  		exit(1);
  	}
  
  	if (f_pllout < 16 || f_pllout > 275) {
  		fprintf(stderr, "Error: PLL output frequency %.3f MHz is outside range 16 MHz - 275 MHz!\n", f_pllout);
  		exit(1);
  	}
  
  	for (int divr = 0; divr <= 15; divr++)
  	{
  		double f_pfd = f_pllin / (divr + 1);
  		if (f_pfd < 10 || f_pfd > 133) continue;
  
  		for (int divf = 0; divf <= divf_max; divf++)
  		{
  			if (simple_feedback)
  			{
  				double f_vco = f_pfd * (divf + 1);
  				if (f_vco < 533 || f_vco > 1066) continue;
  
  				for (int divq = 1; divq <= 6; divq++)
  				{
  					double fout = f_vco * exp2(-divq);
  
  					if (fabs(fout - f_pllout) < fabs(best_fout - f_pllout) || !found_something) {
  						best_fout = fout;
  						best_divr = divr;
  						best_divf = divf;
  						best_divq = divq;
  						found_something = true;
  					}
  				}
  			}
  			else
  			{
  				for (int divq = 1; divq <= 6; divq++)
  				{
  					double f_vco = f_pfd * (divf + 1) * exp2(divq);
  					if (f_vco < 533 || f_vco > 1066) continue;
  
  					double fout = f_vco * exp2(-divq);
  
  					if (fabs(fout - f_pllout) < fabs(best_fout - f_pllout) || !found_something) {
  						best_fout = fout;
  						best_divr = divr;
  						best_divf = divf;
  						best_divq = divq;
  						found_something = true;
  					}
  				}
  			}
  		}
  	}
  
  	double f_pfd = f_pllin / (best_divr + 1);;
  	double f_vco = f_pfd * (best_divf + 1);
  
  	int filter_range =
  			f_pfd < 17 ? 1 :
  			f_pfd < 26 ? 2 :
  			f_pfd < 44 ? 3 :
  			f_pfd < 66 ? 4 :
  			f_pfd < 101 ? 5 : 6;
  
  	if (!simple_feedback)
  		f_vco *= exp2(best_divq);
  
  	if (!found_something) {
  		fprintf(stderr, "Error: No valid configuration found!\n");
  		exit(1);
  	}
  
  	if (!quiet)
  	{
  		printf("\n");
  
  		printf("F_PLLIN:  %8.3f MHz (given)\n", f_pllin);
  		printf("F_PLLOUT: %8.3f MHz (requested)\n", f_pllout);
  		printf("F_PLLOUT: %8.3f MHz (achieved)\n", best_fout);
  
  		printf("\n");
  
  		printf("FEEDBACK: %s\n", simple_feedback ? "SIMPLE" : "NON_SIMPLE");
  		printf("F_PFD: %8.3f MHz\n", f_pfd);
  		printf("F_VCO: %8.3f MHz\n", f_vco);
  
  		printf("\n");
  
  		printf("DIVR: %2d (4'b%s)\n", best_divr, binstr(best_divr, 4));
  		printf("DIVF: %2d (7'b%s)\n", best_divf, binstr(best_divf, 7));
  		printf("DIVQ: %2d (3'b%s)\n", best_divq, binstr(best_divq, 3));
  
  		printf("\n");
  
  		printf("FILTER_RANGE: %d (3'b%s)\n", filter_range, binstr(filter_range, 3));
  
  		printf("\n");
  	}
  
  	// save PLL configuration as file
  	if (filename != NULL)
  	{
  		// open file for writing
  		FILE *f;
  		f = fopen(filename, "w");
  
  		if (save_as_module)
  		{
  			// save PLL configuration as Verilog module
  
  			// header
  			fprintf(f, "/**\n * PLL configuration\n *\n"
  						" * This Verilog module was generated automatically\n"
  						" * using the icepll tool from the IceStorm project.\n"
  						" * Use at your own risk.\n"
  						" *\n"
  						" * Given input frequency:      %8.3f MHz\n"
  						" * Requested output frequency: %8.3f MHz\n"
  						" * Achieved output frequency:  %8.3f MHz\n"
  						" */\n\n",
  						f_pllin, f_pllout, best_fout);
  
  			// generate Verilog module
  			fprintf(f,  "module %s(\n"
  						"\tinput  clock_in,\n"
  						"\toutput clock_out,\n"
  						"\toutput locked\n"
  						"\t);\n\n", (module_name ? module_name : "pll")
  					);
  
  			// save iCE40 PLL tile configuration
  			fprintf(f, "SB_PLL40_CORE #(\n");
  			fprintf(f, "\t\t.FEEDBACK_PATH(\"%s\"),\n", (simple_feedback ? "SIMPLE" : "NON_SIMPLE"));
  			fprintf(f, "\t\t.DIVR(4'b%s),\t\t"      "// DIVR = %2d\n", binstr(best_divr, 4), best_divr);
  			fprintf(f, "\t\t.DIVF(7'b%s),\t"        "// DIVF = %2d\n", binstr(best_divf, 7), best_divf);
  			fprintf(f, "\t\t.DIVQ(3'b%s),\t\t"      "// DIVQ = %2d\n", binstr(best_divq, 3), best_divq);
  			fprintf(f, "\t\t.FILTER_RANGE(3'b%s)\t" "// FILTER_RANGE = %d\n", binstr(filter_range, 3), filter_range);
  			fprintf(f, "\t) uut (\n"
  						"\t\t.LOCK(locked),\n"
  						"\t\t.RESETB(1'b1),\n"
  						"\t\t.BYPASS(1'b0),\n"
  						"\t\t.REFERENCECLK(clock_in),\n"
  						"\t\t.PLLOUTCORE(clock_out)\n"
  						"\t\t);\n\n"
  					);
  
  			fprintf(f, "endmodule\n");
  		}
  		else
  		{
  			// only save PLL configuration values
  
  			// header
  			fprintf(f, "/**\n * PLL configuration\n *\n"
  						" * This Verilog header file was generated automatically\n"
  						" * using the icepll tool from the IceStorm project.\n"
  						" * It is intended for use with FPGA primitives SB_PLL40_CORE,\n"
  						" * SB_PLL40_PAD, SB_PLL40_2_PAD, SB_PLL40_2F_CORE or SB_PLL40_2F_PAD.\n"
  						" * Use at your own risk.\n"
  						" *\n"
  						" * Given input frequency:      %8.3f MHz\n"
  						" * Requested output frequency: %8.3f MHz\n"
  						" * Achieved output frequency:  %8.3f MHz\n"
  						" */\n\n",
  						f_pllin, f_pllout, best_fout);
  
  			// PLL configuration
  			fprintf(f, ".FEEDBACK_PATH(\"%s\"),\n", (simple_feedback ? "SIMPLE" : "NON_SIMPLE"));
  			fprintf(f, ".DIVR(4'b%s),\t\t"      "// DIVR = %2d\n", binstr(best_divr, 4), best_divr);
  			fprintf(f, ".DIVF(7'b%s),\t"        "// DIVF = %2d\n", binstr(best_divf, 7), best_divf);
  			fprintf(f, ".DIVQ(3'b%s),\t\t"      "// DIVQ = %2d\n", binstr(best_divq, 3), best_divq);
  			fprintf(f, ".FILTER_RANGE(3'b%s)\t" "// FILTER_RANGE = %d\n", binstr(filter_range, 3), filter_range);
  		}
  
  		fclose(f);
  
  		printf("PLL configuration written to: %s\n", filename);
  	}
  
  	return 0;
  }
  