added upstream URL http://www.kinetic.co.uk/Trilby.php
[trilby-hat-fpga] / ecp5pll.sv
1 // (c)EMARD
2 // License=BSD
3
4 // parametric ECP5 PLL generator in systemverilog
5 // actual frequency can be equal or higher than requested
6 // to see actual frequencies
7 // trellis log/stdout : search for "MHz", "Derived", "frequency"
8 // to see actual phase shifts
9 // diamond log/*.mrp  : search for "Phase", "Desired"
10
11 module ecp5pll
12 #(
13   parameter integer in_hz      =  25000000,
14   parameter integer out0_hz    =  25000000,
15   parameter integer out0_deg   =         0, // keep 0
16   parameter integer out0_tol_hz=         0, // tolerance: if freq differs more, then error
17   parameter integer out1_hz    =         0,
18   parameter integer out1_deg   =         0,
19   parameter integer out1_tol_hz=         0,
20   parameter integer out2_hz    =         0,
21   parameter integer out2_deg   =         0,
22   parameter integer out2_tol_hz=         0,
23   parameter integer out3_hz    =         0,
24   parameter integer out3_deg   =         0,
25   parameter integer out3_tol_hz=         0,
26   parameter integer reset_en   =         0,
27   parameter integer standby_en =         0,
28   parameter integer dynamic_en =         0
29 )
30 (
31   input        clk_i,
32   output [3:0] clk_o,
33   input        reset,
34   input        standby,
35   input  [1:0] phasesel,
36   input        phasedir, phasestep, phaseloadreg,
37   output       locked
38 );
39
40   localparam PFD_MIN =   3125000;
41   localparam PFD_MAX = 400000000;
42   localparam VCO_MIN = 400000000;
43   localparam VCO_MAX = 800000000;
44   localparam VCO_OPTIMAL = (VCO_MIN+VCO_MAX)/2;
45
46   function integer abs(input integer x);
47     abs = x > 0 ? x : -x;
48   endfunction
49
50   function integer F_ecp5pll(input integer x);
51     integer input_div, input_div_min, input_div_max;
52     integer output_div, output_div_min, output_div_max;
53     integer feedback_div, feedback_div_min, feedback_div_max;
54     integer fvco, fout;
55     integer error, error_prev;
56     integer params_fvco;
57     integer div1, div2, div3;
58
59     integer params_refclk_div;
60     integer params_feedback_div;
61     integer params_output_div;
62
63     params_fvco = 0;
64     error_prev = 999999999;
65     input_div_min = in_hz/PFD_MAX;
66     if(input_div_min < 1)
67       input_div_min = 1;
68     input_div_max = in_hz/PFD_MIN;
69     if(input_div_max > 128)
70       input_div_max = 128;
71     for(input_div = input_div_min; input_div <= input_div_max; input_div=input_div+1)
72     begin
73       if(out0_hz / 1000000 * input_div < 2000)
74         feedback_div = out0_hz * input_div / in_hz;
75       else
76         feedback_div = out0_hz / in_hz * input_div;
77       feedback_div_min = feedback_div;
78       feedback_div_max = feedback_div+1;
79       if(feedback_div_min < 1)
80         feedback_div_min = 1;
81       if(feedback_div_max > 80)
82         feedback_div_max = 80;
83       for(feedback_div = feedback_div_min; feedback_div <= feedback_div_max; feedback_div = feedback_div+1)
84       begin
85         output_div_min = (VCO_MIN/feedback_div) / (in_hz/input_div);
86         if(output_div_min < 1)
87           output_div_min = 1;
88         output_div_max = (VCO_MAX/feedback_div) / (in_hz/input_div);
89         if(output_div_max > 128)
90           output_div_max = 128;
91         fout = in_hz * feedback_div / input_div;
92         for(output_div = output_div_min; output_div <= output_div_max; output_div=output_div+1)
93         begin
94           fvco = fout * output_div;
95           error = abs(fout-out0_hz)
96                 + (out1_hz > 0 ? abs(fvco/(fvco >= out1_hz ? fvco/out1_hz : 1)-out1_hz) : 0)
97                 + (out2_hz > 0 ? abs(fvco/(fvco >= out2_hz ? fvco/out2_hz : 1)-out2_hz) : 0)
98                 + (out3_hz > 0 ? abs(fvco/(fvco >= out3_hz ? fvco/out3_hz : 1)-out3_hz) : 0);
99           if( error < error_prev
100           || (error == error_prev && abs(fvco-VCO_OPTIMAL) < abs(params_fvco-VCO_OPTIMAL)) )
101           begin
102             error_prev              = error;
103             params_refclk_div       = input_div;
104             params_feedback_div     = feedback_div;
105             params_output_div       = output_div;
106             params_fvco             = fvco;
107           end
108         end
109       end
110     end
111     // FIXME in the future when yosys supports struct
112     if(x==0)
113       F_ecp5pll = params_refclk_div;
114     if(x==1)
115       F_ecp5pll = params_feedback_div;
116     if(x==2)
117       F_ecp5pll = params_output_div;
118   endfunction
119
120   function integer F_primary_phase(input integer output_div, deg);
121     integer phase_compensation;
122     integer phase_count_x8;
123
124     phase_compensation = (output_div+1)/2*8-8+output_div/2*8; // output_div/2*8 = 180 deg shift
125     phase_count_x8     = phase_compensation + 8*output_div*deg/360;
126     if(phase_count_x8 > 1023)
127       phase_count_x8 = phase_count_x8 % (output_div*8); // wraparound 360 deg
128     F_primary_phase = phase_count_x8;
129   endfunction
130
131   // FIXME it is inefficient to call F_ecp5pll multiple times
132   localparam params_refclk_div       = F_ecp5pll(0);
133   localparam params_feedback_div     = F_ecp5pll(1);
134   localparam params_output_div       = F_ecp5pll(2);
135   localparam params_fout             = in_hz * params_feedback_div / params_refclk_div;
136   localparam params_fvco             = params_fout * params_output_div;
137
138   localparam params_primary_phase_x8 = F_ecp5pll(3);
139   localparam params_primary_cphase   = F_primary_phase(params_output_div, out0_deg) / 8;
140   localparam params_primary_fphase   = F_primary_phase(params_output_div, out0_deg) % 8;
141
142   function integer F_secondary_divisor(input integer sfreq);
143     F_secondary_divisor = 1;
144     if(sfreq > 0)
145       if(params_fvco >= sfreq)
146         F_secondary_divisor = params_fvco/sfreq;
147   endfunction
148
149   function integer F_secondary_phase(input integer sfreq, sphase);
150     integer div, freq;
151     integer phase_compensation, phase_count_x8;
152
153     phase_count_x8 = 0;
154     if(sfreq > 0)
155     begin
156       div = 1;
157       if(params_fvco >= sfreq)
158         div = params_fvco/sfreq;
159       freq = params_fvco/div;
160       phase_compensation = div*8-8;
161       phase_count_x8 = phase_compensation + 8*div*sphase/360;
162       if(phase_count_x8 > 1023)
163         phase_count_x8 = phase_count_x8 % (div*8); // wraparound 360 deg
164     end
165
166     F_secondary_phase = phase_count_x8;
167   endfunction
168
169   localparam params_secondary1_div      = F_secondary_divisor(out1_hz);
170   localparam params_secondary1_cphase   = F_secondary_phase  (out1_hz, out1_deg) / 8;
171   localparam params_secondary1_fphase   = F_secondary_phase  (out1_hz, out1_deg) % 8;
172   localparam params_secondary2_div      = F_secondary_divisor(out2_hz);
173   localparam params_secondary2_cphase   = F_secondary_phase  (out2_hz, out2_deg) / 8;
174   localparam params_secondary2_fphase   = F_secondary_phase  (out2_hz, out2_deg) % 8;
175   localparam params_secondary3_div      = F_secondary_divisor(out3_hz);
176   localparam params_secondary3_cphase   = F_secondary_phase  (out3_hz, out3_deg) / 8;
177   localparam params_secondary3_fphase   = F_secondary_phase  (out3_hz, out3_deg) % 8;
178
179   // check if generated frequencies are out of range
180   localparam error_out0_hz =               abs(out0_hz - params_fout)                         > out0_tol_hz;
181   localparam error_out1_hz = out1_hz > 0 ? abs(out1_hz - params_fvco / params_secondary1_div) > out1_tol_hz : 0;
182   localparam error_out2_hz = out2_hz > 0 ? abs(out2_hz - params_fvco / params_secondary2_div) > out2_tol_hz : 0;
183   localparam error_out3_hz = out3_hz > 0 ? abs(out3_hz - params_fvco / params_secondary3_div) > out3_tol_hz : 0;
184   // diamond: won't compile this, comment it out. Workaround follows using division by zero
185
186   if(error_out0_hz) $error("out0_hz tolerance exceeds out0_tol_hz");
187   if(error_out1_hz) $error("out1_hz tolerance exceeds out1_tol_hz");
188   if(error_out2_hz) $error("out2_hz tolerance exceeds out2_tol_hz");
189   if(error_out3_hz) $error("out3_hz tolerance exceeds out3_tol_hz");
190
191   // diamond: trigger error with division by zero, doesn't accept $error()
192   localparam trig_out0_hz = error_out0_hz ? 1/0 : 0;
193   localparam trig_out1_hz = error_out1_hz ? 1/0 : 0;
194   localparam trig_out2_hz = error_out2_hz ? 1/0 : 0;
195   localparam trig_out3_hz = error_out3_hz ? 1/0 : 0;
196
197   wire [1:0] PHASESEL_HW = phasesel-1;
198   wire CLKOP; // internal
199
200   // TODO: frequencies in MHz if passed as "attributes"
201   // will appear in diamond *.mrp file like "Output Clock(P) Frequency (MHz):"
202   // but I don't know how to pass string parameters for this:
203   // (* FREQUENCY_PIN_CLKI="025.000000" *)
204   // (* FREQUENCY_PIN_CLKOP="023.345678" *)
205   // (* FREQUENCY_PIN_CLKOS="034.234567" *)
206   // (* FREQUENCY_PIN_CLKOS2="111.345678" *)
207   // (* FREQUENCY_PIN_CLKOS3="123.456789" *)
208   (* ICP_CURRENT="12" *) (* LPF_RESISTOR="8" *) (* MFG_ENABLE_FILTEROPAMP="1" *) (* MFG_GMCREF_SEL="2" *)
209   EHXPLLL
210   #(
211     .CLKI_DIV     (params_refclk_div),
212     .CLKFB_DIV    (params_feedback_div),
213     .FEEDBK_PATH  ("CLKOP"),
214
215     .OUTDIVIDER_MUXA("DIVA"),
216     .CLKOP_ENABLE ("ENABLED"),
217     .CLKOP_DIV    (params_output_div),
218     .CLKOP_CPHASE (params_primary_cphase),
219     .CLKOP_FPHASE (params_primary_fphase),
220
221     .OUTDIVIDER_MUXB("DIVB"),
222     .CLKOS_ENABLE (out1_hz > 0 ? "ENABLED" : "DISABLED"),
223     .CLKOS_DIV    (params_secondary1_div),
224     .CLKOS_CPHASE (params_secondary1_cphase),
225     .CLKOS_FPHASE (params_secondary1_fphase),
226
227     .OUTDIVIDER_MUXC("DIVC"),
228     .CLKOS2_ENABLE(out2_hz > 0 ? "ENABLED" : "DISABLED"),
229     .CLKOS2_DIV   (params_secondary2_div),
230     .CLKOS2_CPHASE(params_secondary2_cphase),
231     .CLKOS2_FPHASE(params_secondary2_fphase),
232
233     .OUTDIVIDER_MUXD("DIVD"),
234     .CLKOS3_ENABLE(out3_hz > 0 ? "ENABLED" : "DISABLED"),
235     .CLKOS3_DIV   (params_secondary3_div),
236     .CLKOS3_CPHASE(params_secondary3_cphase),
237     .CLKOS3_FPHASE(params_secondary3_fphase),
238
239     .INTFB_WAKE   ("DISABLED"),
240     .STDBY_ENABLE (standby_en ? "ENABLED" : "DISABLED"),
241     .PLLRST_ENA   (  reset_en ? "ENABLED" : "DISABLED"),
242     .DPHASE_SOURCE(dynamic_en ? "ENABLED" : "DISABLED"),
243     .PLL_LOCK_MODE(0)
244   )
245   pll_inst
246   (
247     .RST(1'b0),
248     .STDBY(1'b0),
249     .CLKI(clk_i),
250     .CLKOP(CLKOP),
251     .CLKOS (clk_o[1]),
252     .CLKOS2(clk_o[2]),
253     .CLKOS3(clk_o[3]),
254     .CLKFB(CLKOP),
255     .CLKINTFB(),
256     .PHASESEL1(PHASESEL_HW[1]),
257     .PHASESEL0(PHASESEL_HW[0]),
258     .PHASEDIR(phasedir),
259     .PHASESTEP(phasestep),
260     .PHASELOADREG(phaseloadreg),
261     .PLLWAKESYNC(1'b0),
262     .ENCLKOP(1'b0),
263     .ENCLKOS(1'b0),
264     .ENCLKOS2(1'b0),
265     .ENCLKOS3(1'b0),
266     .LOCK(locked)
267   );
268   assign clk_o[0] = CLKOP;
269
270 endmodule