From 619adcc915cbf7b7ded0992cd08f2438957e3a22 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Thu, 30 Dec 2021 21:14:38 +0100 Subject: [PATCH 1/1] add FPGA generated clocks mhz_16, mhz_96 --- Makefile | 2 +- ecp5pll.sv | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++++ i2c.v | 40 +++++++- 3 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 ecp5pll.sv diff --git a/Makefile b/Makefile index ff82a9d..34477b7 100644 --- 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 index 0000000..e78b3db --- /dev/null +++ b/ecp5pll.sv @@ -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 --- 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; -- 2.20.1