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 &params, 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 &params, 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";
+
+}