Merge pull request #54 from C-Elegans/ecppll Create a tool like icepll for the ECP5
diff --git a/libtrellis/.gitignore b/libtrellis/.gitignore index 0a79326..245392f 100644 --- a/libtrellis/.gitignore +++ b/libtrellis/.gitignore
@@ -18,3 +18,6 @@ install_manifest.txt ecppack ecpunpack +ecppll +libtrellis.dylib +*~
diff --git a/libtrellis/CMakeLists.txt b/libtrellis/CMakeLists.txt index 1596348..ff88d0a 100644 --- a/libtrellis/CMakeLists.txt +++ b/libtrellis/CMakeLists.txt
@@ -131,10 +131,15 @@ target_link_libraries(ecpunpack trellis ${Boost_LIBRARIES} ${link_param}) setup_rpath(ecpunpack) +add_executable(ecppll ${INCLUDE_FILES} tools/ecppll.cpp) +target_compile_definitions(ecppll PRIVATE TRELLIS_PREFIX="${CMAKE_INSTALL_PREFIX}") +target_link_libraries(ecppll trellis ${Boost_LIBRARIES} ${link_param}) +setup_rpath(ecppll) + if (BUILD_SHARED) - install(TARGETS trellis ecppack ecpunpack LIBRARY DESTINATION lib RUNTIME DESTINATION bin) + install(TARGETS trellis ecppack ecppll ecpunpack LIBRARY DESTINATION lib RUNTIME DESTINATION bin) else() - install(TARGETS ecppack ecpunpack RUNTIME DESTINATION bin) + install(TARGETS ecppack ecpunpack ecppll RUNTIME DESTINATION bin) endif() install(DIRECTORY ../database DESTINATION share/trellis PATTERN ".git" EXCLUDE) install(DIRECTORY ../misc DESTINATION share/trellis)
diff --git a/libtrellis/tools/ecppll.cpp b/libtrellis/tools/ecppll.cpp new file mode 100644 index 0000000..84e0a00 --- /dev/null +++ b/libtrellis/tools/ecppll.cpp
@@ -0,0 +1,360 @@ + + + +/* I was unable to find good information on how the PLL dividers work +in the ECP5 PLL Design and Usage Guide, so I ran several frequencies +through Lattice's clarity designer, and was surprised with what I +found: +| Input | Output | refclk | feedback | output | fvco | +| 12 | 48 | 1 | 4 | 12 | 576 | +| 12 | 60 | 1 | 5 | 10 | 600 | +| 20 | 30 | 2 | 3 | 20 | 600 | +| 45 | 30 | 3 | 2 | 20 | 600 | +| 100 | 400 | 1 | 4 | 1 | 400 | +| 200 | 400 | 1 | 2 | 2 | 800 | +| 50 | 400 | 1 | 8 | 2 | 800 | +| 70 | 40 | 7 | 4 | 15 | 600 | +| 12 | 36 | 1 | 3 | 18 | 648 | +| 12 | 96 | 1 | 8 | 6 | 576 | +| 90 | 40 | 9 | 4 | 15 | 600 | +| 90 | 50 | 9 | 5 | 13 | 650 | +| 43 | 86 | 1 | 2 | 7 | 602 | + +it appears that +f_pfd = f_in/refclk +f_vco = f_pfd * feedback * output +f_out = f_vco / output + */ + +#define INPUT_MIN 8.0f +#define INPUT_MAX 400.0f +#define OUTPUT_MIN 10.0f +#define OUTPUT_MAX 400.0f +#define PFD_MIN 3.125f +#define PFD_MAX 400.0f +#define VCO_MIN 400.0f +#define VCO_MAX 800.0f +#include <iostream> +#include <limits> +#include <fstream> +#include <boost/program_options.hpp> +using namespace std; + +enum class mode{ + SIMPLE, + HIGHRES +}; + +struct secondary_params{ + bool enabled; + int div; + int cphase; + int fphase; + + float freq; + float phase; +}; + + +struct pll_params{ + enum mode mode; + int refclk_div; + int feedback_div; + int output_div; + int primary_cphase; + + secondary_params secondary[3]; + + float fout; + float fvco; + + pll_params() :mode(mode::SIMPLE) { + for(int i=0;i<3;i++){ + secondary[i].enabled = false; + primary_cphase = 9; + } + } +}; + +pll_params calc_pll_params(float input, float output); +pll_params calc_pll_params_highres(float input, float output); +void generate_secondary_output(pll_params ¶ms, int channel, float frequency, float phase); +void write_pll_config(pll_params params, const char* name, ofstream& file); + +int main(int argc, char** argv){ + namespace po = boost::program_options; + po::options_description options("Allowed options"); + + options.add_options()("help,h", "show help"); + options.add_options()("input,i", po::value<float>(), "Input frequency in MHz"); + options.add_options()("output,o", po::value<float>(), "Output frequency in MHz"); + options.add_options()("s1", po::value<float>(), "Secondary Output frequency in MHz"); + options.add_options()("p1", po::value<float>()->default_value(0), "Secondary Output frequency in MHz"); + options.add_options()("s2", po::value<float>(), "Secondary Output(2) frequency in MHz"); + options.add_options()("p2", po::value<float>()->default_value(0), "Secondary Output(2) frequency in MHz"); + options.add_options()("s3", po::value<float>(), "Secondary Output(3) frequency in MHz"); + options.add_options()("p3", po::value<float>()->default_value(0), "Secondary Output(3) frequency in MHz"); + options.add_options()("file,f", po::value<string>(), "Output to file"); + options.add_options()("highres", "Use secondary PLL output for higher frequency resolution"); + + po::variables_map vm; + po::parsed_options parsed = po::command_line_parser(argc, argv).options(options).run(); + po::store(parsed, vm); + po::notify(vm); + + if(vm.count("help")){ + cerr << "Project Trellis - Open Source Tools for ECP5 FPGAs" << endl; + cerr << "ecpunpack: ECP5 bitstream to text config converter" << endl; + cerr << endl; + cerr << "Copyright (C) 2018 David Shah <david@symbioticeda.com>" << endl; + cerr << endl; + cerr << options << endl; + return 1; + } + if(vm.count("input") != 1 || vm.count("output") != 1){ + cerr << "Error: missing input or output frequency!\n"; + return 1; + } + float inputf = vm["input"].as<float>(); + float outputf = vm["output"].as<float>(); + if(inputf < INPUT_MIN || inputf > INPUT_MAX){ + cerr << "Input frequency " << inputf << "MHz not in range (" << INPUT_MIN << "MHz, " << INPUT_MAX << "MHz)\n"; + return 1; + } + if(outputf < OUTPUT_MIN || outputf > OUTPUT_MAX){ + cerr << "Output frequency " << outputf << "MHz not in range (" << OUTPUT_MIN << "MHz, " << OUTPUT_MAX << "MHz)\n"; + return 1; + } + pll_params params; + if(vm.count("highres")){ + if(vm.count("s1") > 0){ + cerr << "Cannot specify secondary frequency in highres mode\n"; + } + params = calc_pll_params_highres(inputf, outputf); + } + else{ + params = calc_pll_params(inputf, outputf); + if(vm.count("s1")) + generate_secondary_output(params, 0, vm["s1"].as<float>(), vm["p1"].as<float>()); + if(vm.count("s2")) + generate_secondary_output(params, 1, vm["s2"].as<float>(), vm["p2"].as<float>()); + if(vm.count("s3")) + generate_secondary_output(params, 2, vm["s3"].as<float>(), vm["p3"].as<float>()); + + } + + cout << "Pll parameters:" << endl; + cout << "Refclk divisor: " << params.refclk_div << endl; + cout << "Feedback divisor: " << params.feedback_div << endl; + cout << "Output divisor: " << params.output_div << endl; + if(params.secondary[0].enabled){ + cout << "Secondary divisor: " << params.secondary[0].div << endl; + cout << "Secondary freq: " << params.secondary[0].freq << endl; + cout << "Secondary phase shift: " << params.secondary[0].phase << endl; + } + if(params.secondary[1].enabled){ + cout << "Secondary(2) divisor: " << params.secondary[1].div << endl; + cout << "Secondary(2) freq: " << params.secondary[1].freq << endl; + cout << "Secondary(2) phase shift: " << params.secondary[1].phase << endl; + } + cout << "VCO frequency: " << params.fvco << endl; + cout << "Output frequency: " << params.fout << endl; + if(vm.count("file")){ + ofstream f; + + f.open(vm["file"].as<string>().c_str()); + + + write_pll_config(params, "pll", f); + + f.close(); + } + +} + +pll_params calc_pll_params(float input, float output){ + float error = std::numeric_limits<float>::max(); + pll_params params; + for(int input_div=1;input_div <= 128; input_div++){ + + float fpfd = input / (float)input_div; + if(fpfd < PFD_MIN || fpfd > PFD_MAX) + continue; + for(int feedback_div=1;feedback_div <= 80; feedback_div++){ + for(int output_div=1;output_div <= 128; output_div++){ + float fvco = fpfd * (float)feedback_div * (float) output_div; + + if(fvco < VCO_MIN || fvco > VCO_MAX) + continue; + + float fout = fvco / (float) output_div; + if(fabsf(fout - output) < error || + (fabsf(fout-output) == error && fabsf(fvco - 600) < fabsf(params.fvco - 600))){ + error = fabsf(fout-output); + params.refclk_div = input_div; + params.feedback_div = feedback_div; + params.output_div = output_div; + params.fout = fout; + params.fvco = fvco; + + // shift the primary by 180 degrees. Lattice seems to do this + float ns_phase = 1/(fout * 1e6) * 0.5; + params.primary_cphase = ns_phase * (fvco * 1e6); + + } + + } + } + } + return params; +} + +pll_params calc_pll_params_highres(float input, float output){ + float error = std::numeric_limits<float>::max(); + pll_params params; + for(int input_div=1;input_div <= 128; input_div++){ + + float fpfd = input / (float)input_div; + if(fpfd < PFD_MIN || fpfd > PFD_MAX) + continue; + for(int feedback_div=1;feedback_div <= 80; feedback_div++){ + for(int output_div=1;output_div <= 128; output_div++){ + float fvco = fpfd * (float)feedback_div * (float) output_div; + + if(fvco < VCO_MIN || fvco > VCO_MAX) + continue; + float ffeedback = fvco / (float) output_div; + if(ffeedback < OUTPUT_MIN || ffeedback > OUTPUT_MAX) + continue; + for(int secondary_div = 1; secondary_div <= 128; secondary_div++){ + float fout = fvco / (float) secondary_div; + if(fabsf(fout - output) < error || + (fabsf(fout-output) == error && fabsf(fvco - 600) < fabsf(params.fvco - 600))){ + error = fabsf(fout-output); + params.mode = mode::HIGHRES; + params.refclk_div = input_div; + params.feedback_div = feedback_div; + params.output_div = output_div; + params.secondary[0].div = secondary_div; + params.secondary[0].enabled = true; + params.secondary[0].freq = fout; + params.fout = fout; + params.fvco = fvco; + + } + } + + } + } + } + return params; +} + + +void generate_secondary_output(pll_params ¶ms, int channel, float frequency, float phase){ + int div = params.fvco/frequency; + float freq = params.fvco/div; + cout << "sdiv " << div << endl; + + float ns_shift = 1/(freq * 1e6) * phase / 360.0; + float phase_count = ns_shift * (params.fvco * 1e6); + int cphase = (int) phase_count; + int fphase = (int) ((phase_count - cphase) * 8); + + float ns_actual = 1/(params.fvco * 1e6) * (cphase + fphase/8.0); + float phase_shift = 360 * ns_actual/ (1/(freq * 1e6)); + + + params.secondary[channel].enabled = true; + params.secondary[channel].div = div; + params.secondary[channel].freq = freq; + params.secondary[channel].phase = phase_shift; + params.secondary[channel].cphase = cphase + params.primary_cphase; + params.secondary[channel].fphase = fphase; + + + +} + +void write_pll_config(pll_params params, const char* name, ofstream& file){ + file << "module " << name << "(input clki, \n"; + for(int i=0;i<3;i++){ + if(!(i==0 && params.mode == mode::HIGHRES) && params.secondary[i].enabled){ + file << " output clks" << i+1 <<",\n"; + } + } + file << " output locked,\n"; + file << " output clko\n"; + file << ");\n"; + file << "wire clkfb;\n"; + file << "wire clkos;\n"; + file << "wire clkop;\n"; + file << "(* ICP_CURRENT=\"12\" *) (* LPF_RESISTOR=\"8\" *) (* MFG_ENABLE_FILTEROPAMP=\"1\" *) (* MFG_GMCREF_SEL=\"2\" *)\n"; + file << "EHXPLLL #(\n"; + file << " .PLLRST_ENA(\"DISABLED\"),\n"; + file << " .INTFB_WAKE(\"DISABLED\"),\n"; + file << " .STDBY_ENABLE(\"DISABLED\"),\n"; + file << " .DPHASE_SOURCE(\"DISABLED\"),\n"; + file << " .CLKOP_FPHASE(0),\n"; + file << " .CLKOP_CPHASE(" << params.primary_cphase << "),\n"; + file << " .OUTDIVIDER_MUXA(\"DIVA\"),\n"; + file << " .CLKOP_ENABLE(\"ENABLED\"),\n"; + file << " .CLKOP_DIV(" << params.output_div << "),\n"; + if(params.secondary[0].enabled){ + file << " .CLKOS_ENABLE(\"ENABLED\"),\n"; + file << " .CLKOS_DIV(" << params.secondary[0].div << "),\n"; + file << " .CLKOS_CPHASE(" << params.secondary[0].cphase << "),\n"; + file << " .CLKOS_FPHASE(" << params.secondary[0].fphase << "),\n"; + } + if(params.secondary[1].enabled){ + file << " .CLKOS2_ENABLE(\"ENABLED\"),\n"; + file << " .CLKOS2_DIV(" << params.secondary[1].div << "),\n"; + file << " .CLKOS2_CPHASE(" << params.secondary[1].cphase << "),\n"; + file << " .CLKOS2_FPHASE(" << params.secondary[1].fphase << "),\n"; + } + if(params.secondary[2].enabled){ + file << " .CLKOS3_ENABLE(\"ENABLED\"),\n"; + file << " .CLKOS3_DIV(" << params.secondary[2].div << "),\n"; + file << " .CLKOS3_CPHASE(" << params.secondary[2].cphase << "),\n"; + file << " .CLKOS3_FPHASE(" << params.secondary[2].fphase << "),\n"; + } + file << " .CLKFB_DIV(" << params.feedback_div << "),\n"; + file << " .CLKI_DIV(" << params.refclk_div <<"),\n"; + file << " .FEEDBK_PATH(\"INT_OP\")\n"; + file << " ) pll_i (\n"; + file << " .CLKI(clki),\n"; + file << " .CLKFB(clkfb),\n"; + file << " .CLKINTFB(clkfb),\n"; + file << " .CLKOP(clkop),\n"; + if(params.secondary[0].enabled){ + if(params.mode == mode::HIGHRES) + file << " .CLKOS(clkos),\n"; + else + file << " .CLKOS(clks1),\n"; + + } + if(params.secondary[1].enabled){ + file << " .CLKOS2(clks2),\n"; + } + if(params.secondary[2].enabled){ + file << " .CLKOS3(clks3),\n"; + } + file << " .RST(1'b0),\n"; + file << " .STDBY(1'b0),\n"; + file << " .PHASESEL0(1'b0),\n"; + file << " .PHASESEL1(1'b0),\n"; + file << " .PHASEDIR(1'b0),\n"; + file << " .PHASESTEP(1'b0),\n"; + file << " .PLLWAKESYNC(1'b0),\n"; + file << " .ENCLKOP(1'b0),\n"; + file << " .LOCK(locked),\n"; + file << " );\n"; + if(params.mode == mode::SIMPLE){ + file << "assign clko = clkop;\n"; + } + else { + file << "assign clko = clkos;\n"; + } + file << "endmodule\n"; + +}