// Top level synthesizable Verilog for LBNL's LLRF controller // Target: XC2S150 // Author: Larry Doolittle // Machine translated from VHDL by vhd2vl, with much hand post-processing `timescale 1ns / 1ns // formal parameters to DESIGN_TOP are the pins of the device module DESIGN_TOP( DA, DAOV, DB, DBOV, DC, DCOV, DD, DDOV, // ADC Inputs SYNC, GLOBAL_RST, CLK, P_Down, // Output pin to power down the bank of ADCs RF_ON, RF_KILL, PD, // 16-bit bi-directional data bus ALE_host, WE_host, RD_host, CS0_host, RDY_host, CK_host, URST, INT_host, DE, OCLK, OSEL, SLEEP, SP_DOUT, SP_DIN, SP_SCLK, SP_CS1, SP_CS2, SP_CS3, SP_CS4, TEST_1, TEST_2, TEST_3, TEST_4 ); input [11:0] DA; input DAOV; // from Forward ADC input [11:0] DB; input DBOV; // from Reflected ADC input [11:0] DC; input DCOV; // from Cavity ADC input [11:0] DD; input DDOV; // from Spare ADC wire [11:0] DA, DB, DC, DD; wire DAOV, DBOV, DCOV, DDOV; // Miscellaneous inputs input SYNC; // external "10 MHz" sync input RF_ON; // external RF gate pulse input GLOBAL_RST; input CLK; wire SYNC, GLOBAL_RST, CLK, RF_ON; // Miscellaneous outputs output P_Down; output RF_KILL; // we can disable the RF from here // host interface inout[15:0] PD; input CK_host, ALE_host, WE_host, RD_host, CS0_host; wire CK_host, ALE_host, WE_host, RD_host, CS0_host; output RDY_host, INT_host; wire RDY_host, INT_host; input URST; // unused, I'm not even sure this is an input wire URST; // DAC control pins output [11:0] DE; // ERROR OUT to DAC reg [11:0] DE; output OCLK; output OSEL; output SLEEP; // Serial port to external ADC/DAC // Synchronous serial ports input SP_DOUT; // to FPGA sport from ADC/DAC output SP_DIN; // from FPGA sport to ADC/DAC output SP_SCLK; output SP_CS1; // CS1 on schematic output SP_CS2; // CS2 on schematic output SP_CS3; // CS3 on schematic output SP_CS4; // CS4 on schematic wire SP_DOUT, SP_DIN, SP_SCLK; wire SP_CS1, SP_CS2, SP_CS3, SP_CS4; // Test points output TEST_1, TEST_2, TEST_3, TEST_4; wire TEST_1; //////////////////////////////////////////////// // internal signal declarations wire [11:0] feedback_output; wire [11:0] decay_trace_dout; wire [9:0] wide_address; reg error_clear_cmd; wire adc_power, rf_gate, ext_trigger_skipped; // output from rf_timer reg [11:0] trace_data; reg [15:0] trace_address, address_at_pulse_end; reg [11:0] da_latch, db_latch, dc_latch, dd_latch; reg [4:0] sync_count; initial sync_count = 5'b0000; // two bits from here get passed to fdbk_loop wire trace_enable, feedback_enable, integrate_enable, decay_write_enable; reg decay_write_squelch; reg RF_ON_sync, quad_sync; wire [20:0] dkcm_bus; reg clk40_missing, clk40_missing_pulse; reg feedforward_we; wire feedforward_start; reg [10:0] feedforward_counter; initial feedforward_counter = 11'b11111110000; wire [8:0] feedforward_rf_addr; wire [7:0] feedforward_rf_dout; wire [7:0] feedforward_host_dout; reg [7:0] feedforward_host_data; reg [13:0] ADDR_reg; reg [7:0] status; reg [8:0] errors; // registers simply set by the host reg [13:0] SET_I; reg [13:0] SET_Q; reg [5:0] wide_trace_config; // configured by host with writes to 0x0028 // reasonable defaults for simple simulations reg trace_select; initial trace_select = 1'b1; reg phase_sense_adjust; initial phase_sense_adjust = 1'b0; reg rising_edge_decay; initial rising_edge_decay = 1'b1; reg continuous_feedforward; initial continuous_feedforward = 1'b0; reg ignore_rf_off; initial ignore_rf_off = 1'b0; reg self_retrigger; initial self_retrigger = 1'b0; reg halt; initial halt = 1'b0; reg [1:0] trace_sel; initial trace_sel = 2'b10; reg feedback_select; initial feedback_select = 1'b1; reg integrate_select; initial integrate_select = 1'b1; reg test_point_enable; initial test_point_enable = 1'b1; reg iq_hand_flip; initial iq_hand_flip = 1'b0; reg dynamic_setpoint; initial dynamic_setpoint = 1'b0; // end of signal declarations wire LO = 1'b 0; wire HI = 1'b 1; wire [11:0] ZERO_12 = 12'b 000000000000; wire [31:0] ZERO_32 = 32'b 00000000000000000000000000000000; wire [7:0] ZERO_8 = 8'b 00000000; // static, currently unused output to host: assign RDY_host = 1'b 1; // not yet used feature to kill the RF if we detect something // wrong from our end. wire RF_KILL = 1'b 0; // pass signal from the timing subsystem to the output pin: wire P_Down = ~adc_power; // buffer the clocks wire CLK_buf; buf u0( CLK_buf, CLK); // $attribute (u0,"XNF-LCA","IBUFG:O,I"); wire CLK_40MHz; buf u1( CLK_40MHz, CLK_buf); // $attribute (u1,"XNF-LCA","BUFG:O,I"); wire CLK_host; buf u3( CLK_host, CK_host); // $attribute (u3,"XNF-LCA","BUFG:O,I"); // ideally this latch would use the IOB flip-flop always @(posedge CLK_host) begin DE <= { ~feedback_output[11], feedback_output[10:0] }; end // convert offset-binary ADC data to signed for arithmetic wire [11:0] M_signed = { ~DC[11],DC[10:0] }; // design components instantiation ramdp1024x12 decay_trace( .ADDRA(trace_address[9:0] ), .RSTA(LO), .ENA(HI), .WEA(decay_write_enable), .CLKA(CLK_40MHz), .DIA(trace_data), //.DOA(void), // read from host .ADDRB(ADDR_reg[9:0] ), .RSTB(LO), .ENB(HI), .WEB(LO), // host write not allowed .CLKB(CLK_host), .DIB(ZERO_12), // not used .DOB(decay_trace_dout)); wire [31:0] wide_trace_din = {da_latch[11:1],db_latch[11:1],dc_latch[11:2] }; wire [31:0] wide_trace_dout; wire [31:0] unused_32; ramdp1024x32 wide_trace( .ADDRA(wide_address), .RSTA(LO), .ENA(HI), .WEA(trace_enable), .CLKA(CLK_40MHz), .DIA(wide_trace_din), .DOA(unused_32), // read from host .ADDRB(ADDR_reg[9:0] ), .RSTB(LO), .ENB(HI), .WEB(LO), // host write not allowed .CLKB(CLK_host), .DIB(ZERO_32), // not used .DOB(wide_trace_dout)); RAMB4_S8_S8 feedforward_table( .ENA(HI), .DIA(ZERO_8), // not used .ADDRA(feedforward_rf_addr), .WEA(LO), // rf reads only .RSTA(GLOBAL_RST), .CLKA(CLK_40MHz), .DOA(feedforward_rf_dout), // read from host .ENB(HI), .DIB(feedforward_host_data ), .ADDRB(ADDR_reg[8:0] ), .WEB(feedforward_we), .RSTB(GLOBAL_RST), .CLKB(CLK_host), .DOB(feedforward_host_dout)); FDBK_LOOP fdbk( .M(M_signed), .SET_I(SET_I), .SET_Q(SET_Q), .dynamic_setpoint(dynamic_setpoint), .dkcm_bus(dkcm_bus), .t_mod_4(sync_count[1:0] ), .feedback_enable(feedback_enable), .integrate_enable(integrate_enable), .feedforward_data(feedforward_rf_dout), .RESET(GLOBAL_RST), .CLK(CLK_40MHz), .fdbk_err_out(feedback_output)); // Note the reversal of din/dout at this instantiation. // The sport implementation (naturally) drives its dout // and reads its din. The pins on the FPGA in the circuit // attach to wires in the system that connect to the // ADC and DAC din and dout, and by reversing din/dout // here, we keep those wires labelled consistently // even on the FPGA. So the FPGA drives the SP_din pin, // and reads the SP_dout pin. reg host_sport_write; wire [15:0] host_sport_odata; sport serial( .write(host_sport_write), .addr(ADDR_reg[1:0] ), .idata(PD), .din(SP_DOUT), .clk(CLK_host), .dout(SP_DIN), .odata(host_sport_odata), .sclk(SP_SCLK), // set the mapping from logical address to physical chip selects .cs0(SP_CS1), .cs1(SP_CS2), .cs2(SP_CS3), .cs3(SP_CS4)); // TIMING CONTROL LOGIC wire trace_run, feedback_run, integrate_run; // output of timer assign trace_enable = trace_run & trace_select; assign feedback_enable = feedback_run & feedback_select; assign integrate_enable = integrate_run & integrate_select; assign decay_write_enable = trace_enable & ~decay_write_squelch; reg host_timer_write; wire [15:0] host_timer_odata; rf_timer rf_timer1( .ck(CLK_40MHz), .quad_enable(quad_sync), .halt(halt), .rf(RF_ON_sync), .self_retrigger(self_retrigger), .ignore_rf_off(ignore_rf_off), .write_pulse(host_timer_write), .host_address(ADDR_reg[2:0] ), .host_data(PD), .host_read(host_timer_odata), .feedback_run(feedback_run), .integrate_run(integrate_run), .feedforward_start(feedforward_start), .trace_enable(trace_run), .adc_power(adc_power), .host_interrupt(INT_host), .rf_gate(rf_gate), // XXX unused .trigger_skipped(ext_trigger_skipped)); // das blinkenlights flasher flasher1( .clk(CLK_host), .err(clk40_missing_pulse), .lampo(TEST_1)); // Feature list: // An incoming 2.5, 5, or 10 MHz signal on SYNC will lock the internal // phase counter sync_count. If the incoming sync signal is missing, // the counter will overflow and the sync_missing flag will be set. // If the incoming sync signal does not stay in phase with CLK_40MHz/4, // sync_error will be set. This might also happen if SYNC rises very // near the rising edge of CLK_40MHz, causing single cycle jitter in // SYNC_sync. If that happens, the software has to set phase_sense_adjust, // which makes this logic listen to a copy of SYNC that is latched on the // falling edge of CLK_40MHz. // // SYNC is latched on both the rising and falling edge of CLK_40MHz, and // it is not possible to put both latches in the Virtex IOB. So timing // is what it is, fortunately there's almost no logic to get in the way. // The system just has to route from the IOB, through one level of logic, // to the two latches with much less than 12 ns skew. // // The incoming RF_ON signal is synchronized to 40 MHz. In rf_timer, a // rising edge is noticed and creates a one-clock-long external_trigger pulse. // This trigger is synchronized to the internal 10 MHz quadrature clock. // The RF_ON signal must be longer than 100ns for this logic to work, // not a problem since it's expected to be 100us to 1000us long. reg SYNC_delayed; always @(posedge CLK_host) begin SYNC_delayed <= SYNC; end reg SYNC_sync, SYNC_last, sync_error, sync_missing; initial SYNC_sync=0; initial SYNC_last=0; wire glitch = SYNC_sync & ~SYNC_last & ~(sync_count[1:0] == 2'b 11); always @(posedge CLK_40MHz) begin RF_ON_sync <= RF_ON; // single point synchronization of external signal // SYNC is asynchronous. It passes through simple asynchonous logic, // and is sampled here in a single latch. If phase_sense_adjust is set, // SYNC is ignored here, and the falling-edge sampled version SYNC_delayed // is used instead. SYNC_sync <= phase_sense_adjust ? SYNC_delayed : SYNC; SYNC_last <= SYNC_sync; sync_count <= (SYNC_sync & ~SYNC_last) ? 5'b 00000 : sync_count + 1'b 1; quad_sync <= sync_count[1:0] == 2'b 11; if (glitch | error_clear_cmd) sync_error <= glitch; if (sync_count[4] | error_clear_cmd ) sync_missing <= sync_count[4]; end // these two processes provide a "clock detect" feature. // If the 40 MHz goes away, or is even erratic, an error bit is raised. // If the 25 MHz CPU clock goes away, there's nobody to tell :-( // Strategy for crossing the clock domain: use a 2-bit Gray code // counter clocked at 40 MHz, which can be sampled at 25 MHz. // The counter will always have advanced one or two 25 ns cycles // between 40 ns clocks. reg [1:0] gray_40MHz; initial gray_40MHz = 2'b0; always @(posedge CLK_40MHz) begin gray_40MHz <= {gray_40MHz[0], ~gray_40MHz[1] }; end reg [1:0] gray_sample, gray_previous; always @(posedge CLK_40MHz) begin gray_sample <= gray_40MHz; // cross clock domains *here* gray_previous <= gray_sample; clk40_missing_pulse <= (gray_previous == gray_sample); if(clk40_missing_pulse | error_clear_cmd) clk40_missing <= clk40_missing_pulse; end always @(posedge CLK_40MHz) begin if(trace_enable) address_at_pulse_end <= trace_address; trace_address <= trace_enable ? (trace_address + 1'b 1) : {16{1'b0}}; end reg daov_latch, dbov_latch, dcov_latch, ddov_latch; initial begin daov_latch=1'b0; dbov_latch=1'b0; dcov_latch=1'b0; ddov_latch=1'b0; end reg ext_trigger_skipped_latch; always @(posedge CLK_40MHz) begin if(~trace_enable | rising_edge_decay & trace_address[10]) decay_write_squelch <= ~trace_enable; if(ext_trigger_skipped | error_clear_cmd) ext_trigger_skipped_latch <= ext_trigger_skipped; da_latch <= DA; db_latch <= DB; dc_latch <= DC; dd_latch <= DD; trace_data <= trace_sel[1] ? ( trace_sel[0] ? DD : DC ) : ( trace_sel[0] ? DB : DA ); if(trace_enable & DAOV | error_clear_cmd) daov_latch <= trace_enable & DAOV; if(trace_enable & DBOV | error_clear_cmd) dbov_latch <= trace_enable & DBOV; if(trace_enable & DCOV | error_clear_cmd) dcov_latch <= trace_enable & DCOV; if(trace_enable & DDOV | error_clear_cmd) ddov_latch <= trace_enable & DDOV; end // 11 bit counter that is used to generate the feedforward address. // ignore bits 1 and 2 to generate the address for the 512 element // feedforward table. Host needs to make sure the value at the // first address is zero, since we sit there when the system is idle. reg feedforward_run; initial feedforward_run = 0; always @(posedge CLK_40MHz) begin feedforward_run <= continuous_feedforward | feedforward_start | (feedforward_run & (feedforward_counter != 11'b11111111111)); feedforward_counter <= feedforward_run ? (feedforward_counter + 1'b 1) : {11{1'b0}}; end assign feedforward_rf_addr = {feedforward_counter[10:3] ,feedforward_counter[0] }; assign wide_address[0] = trace_address[0] ; assign wide_address[1] = trace_address[1] ; assign wide_address[2] = ~wide_trace_config[0] ? trace_address[2] : trace_address[10] ; assign wide_address[3] = ~wide_trace_config[1] ? trace_address[3] : trace_address[11] ; assign wide_address[4] = ~wide_trace_config[2] ? trace_address[4] : trace_address[12] ; assign wide_address[5] = ~wide_trace_config[3] ? trace_address[5] : trace_address[13] ; assign wide_address[6] = ~wide_trace_config[4] ? trace_address[6] : trace_address[14] ; assign wide_address[7] = ~wide_trace_config[5] ? trace_address[7] : trace_address[15] ; assign wide_address[8] = trace_address[8] ; assign wide_address[9] = trace_address[9] ; // TEST_1 comes from flasher reg TEST_2, TEST_3; always @(posedge CLK_40MHz) begin TEST_2 <= SYNC_sync; TEST_3 <= adc_power; end wire TEST_4 = test_point_enable; // KCM load logic reg [11:0] KCM_load_data; reg [2:0] KCM_load_addr; reg KCM_load_cmd, KCM_load_delayed; wire KCM_load_busy; wire KCM_load_start = (KCM_load_cmd) & (~KCM_load_delayed) & (~KCM_load_busy); dkcm_controller kcm_stuff( .clk(CLK_host), .dkcm_bus(dkcm_bus), .konstant(KCM_load_data), .load(KCM_load_start), .addr(KCM_load_addr), .busy(KCM_load_busy) ); reg handshake_error, handshake_token; always @(posedge CLK_host) begin if(trace_run & handshake_token | error_clear_cmd) handshake_error <= trace_run & handshake_token; end wire host_write_cycle = (~WE_host) & (~CS0_host); wire host_read_cycle = (~RD_host) & (~CS0_host); // host interface control always @(posedge CLK_host) begin if(ALE_host & ~CS0_host) ADDR_reg <= PD[15:2] ; // ignore lower two bits, we don't support byte or halfword operations on // this virtual address space. if(host_write_cycle) begin if((ADDR_reg[13:4] == 10'b 0000000000)) begin if((ADDR_reg[3:0] == 4'b 1000)) begin // 0x0020 SET_I <= PD[15:2] ; end if((ADDR_reg[3:0] == 4'b 1001)) begin // 0x0024 SET_Q <= PD[15:2] ; end if((ADDR_reg[3:0] == 4'b 1010)) begin // 0x0028 trace_select <= PD[0] ; phase_sense_adjust <= PD[1] ; rising_edge_decay <= PD[2] ; continuous_feedforward <= PD[3] ; ignore_rf_off <= PD[4] ; self_retrigger <= PD[5] ; halt <= PD[6] ; trace_sel <= PD[8:7] ; feedback_select <= PD[9] ; integrate_select <= PD[10] ; test_point_enable <= PD[11] ; iq_hand_flip <= PD[12] ; dynamic_setpoint <= PD[13] ; end if((ADDR_reg[3:0] == 4'b 1011)) begin // 0x002c wide_trace_config <= PD[5:0] ; end if((ADDR_reg[3:2] == 2'b 11)) begin // 0x0030-0x003c KCM-xxx KCM_load_data <= PD[15:4] ; KCM_load_addr <= {1'b0, ADDR_reg[1:0]}; end end end // write-enable logic for the KCMs, but in the Host clock domain. // It's up to the ??? module to move these signals to the // 40 MHz signal processing domain in which the KCMs live. KCM_load_cmd <= host_write_cycle & (ADDR_reg[13:2] == 12'b 000000000011); KCM_load_delayed <= KCM_load_cmd; // write-enable logic for the serial port. Just like the KCM write // strobes above, but no problems with clock domain since the serial // port runs on the host clock. host_sport_write <= host_write_cycle & (ADDR_reg[13:2] == 12'b 000000000110); // write-enable logic for the bank of timer registers. // This decoding matches for 0x0080 through 0x00bc, of which the 8 words at // 0x0080 through 0x09c are actually used. host_timer_write <= host_write_cycle & (ADDR_reg[13:4] == 10'b 0000000010); // write-enable logic for the feedforward table. This goes into one half of // a dual port RAM. As long as writes don't take place during the RF pulse, // there's no chance of using corrupted data for the feedforward. Maybe put // an error bit on this coincidence? // Enable on addresses 0x3000 through 0x3ffc, although only 512 elements // are implemented, so beyond 0x3000 through 0x37fc the address wraps. feedforward_we <= host_write_cycle & (ADDR_reg[13:10] == 4'b 0011); if (host_write_cycle & (ADDR_reg[13:10] == 4'b 0011)) feedforward_host_data <= PD[7:0]; // It probably doesn't matter, but give a consistent snapshot of the // status bits back to the host; this way none of them will change // in the middle of the read transaction. if (host_read_cycle & (ADDR_reg == 14'b 00000000000010)) begin // nothing end else begin status <= {RF_ON_sync,SYNC_sync,KCM_load_busy,URST,gray_sample,gray_previous}; end // Reading the error register (0x000c) causes its bits to clear. // Latch the error bits in a register so we can see what the bits // were before the clear happened. error_clear_cmd <= host_read_cycle & (ADDR_reg == 14'b 00000000000011); if (host_read_cycle & (ADDR_reg == 14'b 00000000000011)) errors <= {handshake_error,sync_error,sync_missing,ext_trigger_skipped_latch,clk40_missing,daov_latch,dbov_latch,dcov_latch,ddov_latch}; // The software is expected to write to the error register (0x000c) (the data // is ignored) at the beginning of its xfer_from_fpga routine, which is triggered // by the interrupt. This puts the handshake logic into "armed" mode, where // handshake_token='1'. If an RF pulse comes along in this mode, the handshake_error // bit is set. The xfer_from_fpga routine will read from error register as the // last thing it does, which both finds out if it acted fast enough to not get // corrupted data, and sets handshake_token back to '0', so the handshake logic // won't complain when the next RF pulse hits. if((host_write_cycle | host_write_cycle) & (ADDR_reg == 14'b 00000000000011)) handshake_token <= host_write_cycle; end reg [15:0] PDi; // asynchronous always @(ADDR_reg or errors or status or address_at_pulse_end or SET_I or host_sport_odata or host_timer_odata or feedforward_host_dout or decay_trace_dout or wide_trace_dout) begin // nearly binary tree to select PD output to host if((ADDR_reg[13:11] == 3'b 000)) begin // 0x0000 through 0x1ffc if((ADDR_reg[10:2] == 9'b 000000000)) begin // 0x0000 through 0x000c if((ADDR_reg[1] == 1'b 0)) begin // 0x0000 through 0x0004 if((ADDR_reg[0] == 1'b 0)) begin // 0x0000 PDi = 16'b 0111011100000001; // product code 0x7701 end else begin // 0x0004 PDi = 16'b 0000000000010011; // firmware version #19 end end else begin // 0x0008 through 0x001c if((ADDR_reg[0] == 1'b 0)) begin // 0x0008 (designed) // status bits, could add more decoding here too PDi = {8'b 00000000,status}; end else begin // 0x000c (designed) PDi = {7'b 0000000,errors}; end end end else begin // 0x0010 through 0x1ffc if((ADDR_reg[10:4] == 7'b 0000000)) begin // 0x0010 through 0x003c if((ADDR_reg[3] == 1'b 0)) begin // 0x0010 through 0x001c PDi = address_at_pulse_end; // 0x0014 (designed) end else begin // 0x0020 through 0x003c PDi = {SET_I,2'b 00}; end end else begin // 0x0040 through 0x1ffc if((ADDR_reg[10:5] == 6'b 000000)) begin // 0x0040 through 0x007c PDi = host_sport_odata; end else begin // 0x0080 through 0x1ffc PDi = host_timer_odata; end end end end else begin // 0x2000 through 0xfffc if((ADDR_reg[13] == 1'b 0)) begin // 0x2000 through 0x7ffc if((ADDR_reg[12] == 1'b 0)) begin // 0x2000 through 0x3ffc PDi = {8'b 00000000,feedforward_host_dout}; // 0x3000 through 0x3ffc allocated, // 0x3000 through 0x37fc implemented end else begin // 0x4000 through 0x7ffc // Don't sign extend the data for buffer readout, // they are 0-4095 offset binary values direct from the ADC. PDi = {4'b 0000,decay_trace_dout}; // read out the decay_trace_buffer end end else begin // 0x8000 through 0xfffc if((ADDR_reg[12] == 1'b 0)) begin // 0x8000 through 0xbffc if((ADDR_reg[11] == 1'b 0)) // 0x8000 through 0x9ffc PDi = {4'b 0000,wide_trace_dout[31:21] ,1'b 0}; else // 0xa000 through 0xbffc PDi = {4'b 0000,wide_trace_dout[20:10] ,1'b 0}; end else // 0xc000 through 0xffff PDi = {4'b 0000,wide_trace_dout[9:0] ,2'b 00}; end end end // Three-state bus assign PD = host_read_cycle ? PDi : 16'b ZZZZZZZZZZZZZZZZ; // DAC controls wire OCLK = ~CLK_40MHz; wire OSEL = sync_count[0] ^ iq_hand_flip; wire SLEEP = LO; endmodule