|  | // | 
|  | //  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; | 
|  | } |