add FPGA generated clocks mhz_16, mhz_96
authorDobrica Pavlinusic <dpavlin@rot13.org>
Thu, 30 Dec 2021 20:14:38 +0000 (21:14 +0100)
committerDobrica Pavlinusic <dpavlin@rot13.org>
Thu, 30 Dec 2021 20:15:49 +0000 (21:15 +0100)
Makefile
ecp5pll.sv [new file with mode: 0644]
i2c.v

index ff82a9d..34477b7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 PROJ:=led
-PROJ=blinky
+PROJ=i2c
 TRELLIS?=/usr/share/trellis
 
 all: ${PROJ}.bit
diff --git a/ecp5pll.sv b/ecp5pll.sv
new file mode 100644 (file)
index 0000000..e78b3db
--- /dev/null
@@ -0,0 +1,270 @@
+// (c)EMARD
+// License=BSD
+
+// parametric ECP5 PLL generator in systemverilog
+// actual frequency can be equal or higher than requested
+// to see actual frequencies
+// trellis log/stdout : search for "MHz", "Derived", "frequency"
+// to see actual phase shifts
+// diamond log/*.mrp  : search for "Phase", "Desired"
+
+module ecp5pll
+#(
+  parameter integer in_hz      =  25000000,
+  parameter integer out0_hz    =  25000000,
+  parameter integer out0_deg   =         0, // keep 0
+  parameter integer out0_tol_hz=         0, // tolerance: if freq differs more, then error
+  parameter integer out1_hz    =         0,
+  parameter integer out1_deg   =         0,
+  parameter integer out1_tol_hz=         0,
+  parameter integer out2_hz    =         0,
+  parameter integer out2_deg   =         0,
+  parameter integer out2_tol_hz=         0,
+  parameter integer out3_hz    =         0,
+  parameter integer out3_deg   =         0,
+  parameter integer out3_tol_hz=         0,
+  parameter integer reset_en   =         0,
+  parameter integer standby_en =         0,
+  parameter integer dynamic_en =         0
+)
+(
+  input        clk_i,
+  output [3:0] clk_o,
+  input        reset,
+  input        standby,
+  input  [1:0] phasesel,
+  input        phasedir, phasestep, phaseloadreg,
+  output       locked
+);
+
+  localparam PFD_MIN =   3125000;
+  localparam PFD_MAX = 400000000;
+  localparam VCO_MIN = 400000000;
+  localparam VCO_MAX = 800000000;
+  localparam VCO_OPTIMAL = (VCO_MIN+VCO_MAX)/2;
+
+  function integer abs(input integer x);
+    abs = x > 0 ? x : -x;
+  endfunction
+
+  function integer F_ecp5pll(input integer x);
+    integer input_div, input_div_min, input_div_max;
+    integer output_div, output_div_min, output_div_max;
+    integer feedback_div, feedback_div_min, feedback_div_max;
+    integer fvco, fout;
+    integer error, error_prev;
+    integer params_fvco;
+    integer div1, div2, div3;
+
+    integer params_refclk_div;
+    integer params_feedback_div;
+    integer params_output_div;
+
+    params_fvco = 0;
+    error_prev = 999999999;
+    input_div_min = in_hz/PFD_MAX;
+    if(input_div_min < 1)
+      input_div_min = 1;
+    input_div_max = in_hz/PFD_MIN;
+    if(input_div_max > 128)
+      input_div_max = 128;
+    for(input_div = input_div_min; input_div <= input_div_max; input_div=input_div+1)
+    begin
+      if(out0_hz / 1000000 * input_div < 2000)
+        feedback_div = out0_hz * input_div / in_hz;
+      else
+        feedback_div = out0_hz / in_hz * input_div;
+      feedback_div_min = feedback_div;
+      feedback_div_max = feedback_div+1;
+      if(feedback_div_min < 1)
+        feedback_div_min = 1;
+      if(feedback_div_max > 80)
+        feedback_div_max = 80;
+      for(feedback_div = feedback_div_min; feedback_div <= feedback_div_max; feedback_div = feedback_div+1)
+      begin
+        output_div_min = (VCO_MIN/feedback_div) / (in_hz/input_div);
+        if(output_div_min < 1)
+          output_div_min = 1;
+        output_div_max = (VCO_MAX/feedback_div) / (in_hz/input_div);
+        if(output_div_max > 128)
+          output_div_max = 128;
+        fout = in_hz * feedback_div / input_div;
+        for(output_div = output_div_min; output_div <= output_div_max; output_div=output_div+1)
+        begin
+          fvco = fout * output_div;
+          error = abs(fout-out0_hz)
+                + (out1_hz > 0 ? abs(fvco/(fvco >= out1_hz ? fvco/out1_hz : 1)-out1_hz) : 0)
+                + (out2_hz > 0 ? abs(fvco/(fvco >= out2_hz ? fvco/out2_hz : 1)-out2_hz) : 0)
+                + (out3_hz > 0 ? abs(fvco/(fvco >= out3_hz ? fvco/out3_hz : 1)-out3_hz) : 0);
+          if( error < error_prev
+          || (error == error_prev && abs(fvco-VCO_OPTIMAL) < abs(params_fvco-VCO_OPTIMAL)) )
+          begin
+            error_prev              = error;
+            params_refclk_div       = input_div;
+            params_feedback_div     = feedback_div;
+            params_output_div       = output_div;
+            params_fvco             = fvco;
+          end
+        end
+      end
+    end
+    // FIXME in the future when yosys supports struct
+    if(x==0)
+      F_ecp5pll = params_refclk_div;
+    if(x==1)
+      F_ecp5pll = params_feedback_div;
+    if(x==2)
+      F_ecp5pll = params_output_div;
+  endfunction
+
+  function integer F_primary_phase(input integer output_div, deg);
+    integer phase_compensation;
+    integer phase_count_x8;
+
+    phase_compensation = (output_div+1)/2*8-8+output_div/2*8; // output_div/2*8 = 180 deg shift
+    phase_count_x8     = phase_compensation + 8*output_div*deg/360;
+    if(phase_count_x8 > 1023)
+      phase_count_x8 = phase_count_x8 % (output_div*8); // wraparound 360 deg
+    F_primary_phase = phase_count_x8;
+  endfunction
+
+  // FIXME it is inefficient to call F_ecp5pll multiple times
+  localparam params_refclk_div       = F_ecp5pll(0);
+  localparam params_feedback_div     = F_ecp5pll(1);
+  localparam params_output_div       = F_ecp5pll(2);
+  localparam params_fout             = in_hz * params_feedback_div / params_refclk_div;
+  localparam params_fvco             = params_fout * params_output_div;
+
+  localparam params_primary_phase_x8 = F_ecp5pll(3);
+  localparam params_primary_cphase   = F_primary_phase(params_output_div, out0_deg) / 8;
+  localparam params_primary_fphase   = F_primary_phase(params_output_div, out0_deg) % 8;
+
+  function integer F_secondary_divisor(input integer sfreq);
+    F_secondary_divisor = 1;
+    if(sfreq > 0)
+      if(params_fvco >= sfreq)
+        F_secondary_divisor = params_fvco/sfreq;
+  endfunction
+
+  function integer F_secondary_phase(input integer sfreq, sphase);
+    integer div, freq;
+    integer phase_compensation, phase_count_x8;
+
+    phase_count_x8 = 0;
+    if(sfreq > 0)
+    begin
+      div = 1;
+      if(params_fvco >= sfreq)
+        div = params_fvco/sfreq;
+      freq = params_fvco/div;
+      phase_compensation = div*8-8;
+      phase_count_x8 = phase_compensation + 8*div*sphase/360;
+      if(phase_count_x8 > 1023)
+        phase_count_x8 = phase_count_x8 % (div*8); // wraparound 360 deg
+    end
+
+    F_secondary_phase = phase_count_x8;
+  endfunction
+
+  localparam params_secondary1_div      = F_secondary_divisor(out1_hz);
+  localparam params_secondary1_cphase   = F_secondary_phase  (out1_hz, out1_deg) / 8;
+  localparam params_secondary1_fphase   = F_secondary_phase  (out1_hz, out1_deg) % 8;
+  localparam params_secondary2_div      = F_secondary_divisor(out2_hz);
+  localparam params_secondary2_cphase   = F_secondary_phase  (out2_hz, out2_deg) / 8;
+  localparam params_secondary2_fphase   = F_secondary_phase  (out2_hz, out2_deg) % 8;
+  localparam params_secondary3_div      = F_secondary_divisor(out3_hz);
+  localparam params_secondary3_cphase   = F_secondary_phase  (out3_hz, out3_deg) / 8;
+  localparam params_secondary3_fphase   = F_secondary_phase  (out3_hz, out3_deg) % 8;
+
+  // check if generated frequencies are out of range
+  localparam error_out0_hz =               abs(out0_hz - params_fout)                         > out0_tol_hz;
+  localparam error_out1_hz = out1_hz > 0 ? abs(out1_hz - params_fvco / params_secondary1_div) > out1_tol_hz : 0;
+  localparam error_out2_hz = out2_hz > 0 ? abs(out2_hz - params_fvco / params_secondary2_div) > out2_tol_hz : 0;
+  localparam error_out3_hz = out3_hz > 0 ? abs(out3_hz - params_fvco / params_secondary3_div) > out3_tol_hz : 0;
+  // diamond: won't compile this, comment it out. Workaround follows using division by zero
+
+  if(error_out0_hz) $error("out0_hz tolerance exceeds out0_tol_hz");
+  if(error_out1_hz) $error("out1_hz tolerance exceeds out1_tol_hz");
+  if(error_out2_hz) $error("out2_hz tolerance exceeds out2_tol_hz");
+  if(error_out3_hz) $error("out3_hz tolerance exceeds out3_tol_hz");
+
+  // diamond: trigger error with division by zero, doesn't accept $error()
+  localparam trig_out0_hz = error_out0_hz ? 1/0 : 0;
+  localparam trig_out1_hz = error_out1_hz ? 1/0 : 0;
+  localparam trig_out2_hz = error_out2_hz ? 1/0 : 0;
+  localparam trig_out3_hz = error_out3_hz ? 1/0 : 0;
+
+  wire [1:0] PHASESEL_HW = phasesel-1;
+  wire CLKOP; // internal
+
+  // TODO: frequencies in MHz if passed as "attributes"
+  // will appear in diamond *.mrp file like "Output Clock(P) Frequency (MHz):"
+  // but I don't know how to pass string parameters for this:
+  // (* FREQUENCY_PIN_CLKI="025.000000" *)
+  // (* FREQUENCY_PIN_CLKOP="023.345678" *)
+  // (* FREQUENCY_PIN_CLKOS="034.234567" *)
+  // (* FREQUENCY_PIN_CLKOS2="111.345678" *)
+  // (* FREQUENCY_PIN_CLKOS3="123.456789" *)
+  (* ICP_CURRENT="12" *) (* LPF_RESISTOR="8" *) (* MFG_ENABLE_FILTEROPAMP="1" *) (* MFG_GMCREF_SEL="2" *)
+  EHXPLLL
+  #(
+    .CLKI_DIV     (params_refclk_div),
+    .CLKFB_DIV    (params_feedback_div),
+    .FEEDBK_PATH  ("CLKOP"),
+
+    .OUTDIVIDER_MUXA("DIVA"),
+    .CLKOP_ENABLE ("ENABLED"),
+    .CLKOP_DIV    (params_output_div),
+    .CLKOP_CPHASE (params_primary_cphase),
+    .CLKOP_FPHASE (params_primary_fphase),
+
+    .OUTDIVIDER_MUXB("DIVB"),
+    .CLKOS_ENABLE (out1_hz > 0 ? "ENABLED" : "DISABLED"),
+    .CLKOS_DIV    (params_secondary1_div),
+    .CLKOS_CPHASE (params_secondary1_cphase),
+    .CLKOS_FPHASE (params_secondary1_fphase),
+
+    .OUTDIVIDER_MUXC("DIVC"),
+    .CLKOS2_ENABLE(out2_hz > 0 ? "ENABLED" : "DISABLED"),
+    .CLKOS2_DIV   (params_secondary2_div),
+    .CLKOS2_CPHASE(params_secondary2_cphase),
+    .CLKOS2_FPHASE(params_secondary2_fphase),
+
+    .OUTDIVIDER_MUXD("DIVD"),
+    .CLKOS3_ENABLE(out3_hz > 0 ? "ENABLED" : "DISABLED"),
+    .CLKOS3_DIV   (params_secondary3_div),
+    .CLKOS3_CPHASE(params_secondary3_cphase),
+    .CLKOS3_FPHASE(params_secondary3_fphase),
+
+    .INTFB_WAKE   ("DISABLED"),
+    .STDBY_ENABLE (standby_en ? "ENABLED" : "DISABLED"),
+    .PLLRST_ENA   (  reset_en ? "ENABLED" : "DISABLED"),
+    .DPHASE_SOURCE(dynamic_en ? "ENABLED" : "DISABLED"),
+    .PLL_LOCK_MODE(0)
+  )
+  pll_inst
+  (
+    .RST(1'b0),
+    .STDBY(1'b0),
+    .CLKI(clk_i),
+    .CLKOP(CLKOP),
+    .CLKOS (clk_o[1]),
+    .CLKOS2(clk_o[2]),
+    .CLKOS3(clk_o[3]),
+    .CLKFB(CLKOP),
+    .CLKINTFB(),
+    .PHASESEL1(PHASESEL_HW[1]),
+    .PHASESEL0(PHASESEL_HW[0]),
+    .PHASEDIR(phasedir),
+    .PHASESTEP(phasestep),
+    .PHASELOADREG(phaseloadreg),
+    .PLLWAKESYNC(1'b0),
+    .ENCLKOP(1'b0),
+    .ENCLKOS(1'b0),
+    .ENCLKOS2(1'b0),
+    .ENCLKOS3(1'b0),
+    .LOCK(locked)
+  );
+  assign clk_o[0] = CLKOP;
+
+endmodule
diff --git a/i2c.v b/i2c.v
index 079b4e3..6c04494 100644 (file)
--- a/i2c.v
+++ b/i2c.v
@@ -1,4 +1,5 @@
 `include "i2c_bridge.v"
+`include "ecp5pll.sv"
 
 module top(
        input clk,
@@ -9,16 +10,47 @@ module top(
        inout tuner_sda,
        inout tuner_scl,
 
+       output mhz_16,mhz_96,
+
+       output exp_pin_3, exp_pin_4,
+       output exp_pin_5, exp_pin_6,
+       output exp_pin_7, exp_pin_8,
+
        output green_led_d7,
        output orange_led_d8,
        output red_led_d5,
        output yellow_led_d6
 );
 
-       assign green_led_d7  = 1;
-       assign orange_led_d8 = 1;
-       assign red_led_d5    = 1;
-       assign yellow_led_d6 = 1;
+       wire [3:0] clocks;
+       ecp5pll
+       #(
+               .in_hz(24000000),
+               .out0_hz(16000000),.out0_tol_hz(0) ,
+               .out1_hz(96000000), .out1_deg( 0), .out1_tol_hz(0)//,
+               //.out2_hz(60000000), .out2_deg(180), .out2_tol_hz(0),
+       )
+       ecp5pll_inst
+       (
+               .clk_i(clk),
+               .clk_o(clocks)
+       );
+
+       assign mhz_16 = clocks[0];
+       assign mhz_96 = clocks[1];
+
+/*
+       assign exp_pin_4 = clocks[0];
+       assign exp_pin_8 = clocks[1];
+*/
+       assign exp_pin_4 = rtc_scl;
+       assign exp_pin_8 = rtc_sda;
+
+       assign green_led_d7  = rtc_scl;
+       assign orange_led_d8 = rtc_sda;
+       assign red_led_d5    = tuner_scl;
+       assign yellow_led_d6 = tuner_sda;
+
 
   localparam bridge_clk_div = 3; // div = 1+2^n, 24/(1+2^2)=4 MHz
   reg [bridge_clk_div:0] bridge_cnt;