// rf_timer.v
// LLRF Timing state machine
// $Id$
// Larry Doolittle, LBNL
// llc-suite Copyright (c) 2004, The Regents of the University of
// California, through Lawrence Berkeley National Laboratory (subject
// to receipt of any required approvals from the U.S. Dept. of Energy).
// All rights reserved.
// Your use of this software is pursuant to a "BSD-style" open
// source license agreement, the text of which is in license.txt
// (md5sum a1e0e81c78f6eba050b0e96996f49fd5) that should accompany
// this file. If the license agreement is not there, or if you
// have questions about the license, please contact Berkeley Lab's
// Technology Transfer Department at TTD@lbl.gov referring to
// "llc-suite (LBNL Ref CR-1988)"
// Target: XC2S150 on SNS LLRF Digital board
// Initial coding: January 18-20, 2002
// wishlist:
// record length of RF pulse, for readback by software
// detect skipped RF triggers
// double buffer ttable, easy with an additional address bit
// outputs: feedback_run, feedforward_run, adc_power
// Input "clk40" is the 40 MHz acquisition clock.
//
// quad_sync is the 10 MHz 1-out-of-4 synchronous enable signal
//
// rf is the rfon input, assumed already synchronized to 40 MHz
//
// halt and trig_fallback are control bits set by the host
//
// write_pulse, host_* are the host interface, whereby the host
// can read and write the 8 x 16-bit timing control registers.
// write_pulse is assumed long compared to the 25 ns cycle, and
// repeated writes to the same register file location are OK.
// host_data must remain valid for 100 ns after write_pulse falls.
//
// Thus, this module is the big exception to the "cross clock domains
// at 60 Hz" rule. While I tried to code and simulate this process
// carefully, it is fundamentally less robust than I want. I think
// I could improve the situation by using SRL16's, like in dac_testpattern.v,
// along with some simple interlock/handshake logic to keep the contents
// from being used during the load sequence. Note that that could,
// potentially, keep a pulse from triggering, if the computer stalled
// in the middle of reloading the table. Could I set up a double-buffer?
// The totalizer_gate section is a total hack. This feature should really
// be accomplished by sliding a couple more phases into the state machine,
// between ADC warmup and start of RF output. Such a change would break
// software compatibility, and is therefore postponed. All we do for
// now is construct a second, simpler timing system to handle just the
// totalizer. Increase the address space from 8 to 16 words to leave
// space for configuring this new, temporary subsystem.
// The totalizer_gate pulse is always 1024 clock pulses long (25.6 us),
// and is delayed from the adc_power rising edge (beginning of main state 3)
// by (16*n+1)*0.1 us, where the 8-bit n is set by writing to
// address 0x00a0 (host_addr == 4'b1000). Thus the delay allocated for
// ADC warmup before phase calibration can range from 0.1 us to 408.1 us.
`timescale 1ns / 1ns
module rf_timer
(
input rst, // interconnect
input host_clk, // interconnect
input [13:0] host_addr, // interconnect
input host_we, // interconnect
input [15:0] host_data, // interconnect
output reg [15:0] host_read, // register TIMERS
input hselect, // select TIMERS
input clk40, // interconnect
input quad_sync, // interconnect
output reg rf_on_sync, // interconnect
input [15:0] totalizer_delay, // register TOTAL_DEL
input [15:0] config_word, // register CONFIG
output feedback_enable, // interconnect
output integrate_enable, // interconnect
output reg feedforward_start, // interconnect
output reg host_interrupt, // interconnect
output trace_enable, // interconnect
output reg trigger_skipped, // interconnect
output reg totalizer_gate, // interconnect
output dds_run, // interconnect
output dds_substitute, // interconnect
output totalizer_channel, // interconnect
output phase_sense_adjust, // interconnect
input rf_on_, // interconnect
output rf_kill_, // interconnect
output power_down, // interconnect
output LED3, // pin d
output LED4 // pin e
);
wire write_pulse = host_we & hselect;
wire trace_select = config_word[0];
assign phase_sense_adjust = config_word[1]; // only used in MEBT configuration
// unused config_word[2];
wire pulse_run = config_word[3]; // initial condition: 0 means don't run until told to!
wire ignore_rf_off = config_word[4];
wire self_retrigger = config_word[5];
// unused config_word[6];
// unused config_word[7];
// unused config_word[8];
wire feedback_select = config_word[9];
wire integrate_select = config_word[10];
wire rf_permit = config_word[11];
wire cw_request = config_word[12];
wire dds_passthru = config_word[13];
assign totalizer_channel = config_word[14];
wire raw_dds_select = config_word[15]; // simply passed to interconnect
reg adc_power;
assign power_down = ~adc_power;
wire halt = ~pulse_run;
assign dds_run = adc_power | dds_passthru;
assign rf_kill_ = ~rf_permit;
assign LED3 = ~adc_power;
assign LED4 = halt;
reg feedback_run, integrate_run, trace_run;
assign feedback_enable = feedback_run & feedback_select;
assign integrate_enable = integrate_run & integrate_select;
wire raw_dds_enable = feedback_run & raw_dds_select;
assign trace_enable = trace_run & trace_select;
assign dds_substitute = dds_passthru | raw_dds_enable;
always @(posedge clk40) rf_on_sync <= rf_on_;
wire rf = rf_on_sync;
reg [2:0] host_address_hold1;
always @(posedge host_clk) if (hselect) host_address_hold1 <= host_addr[2:0];
// Give ourselves oodles of time for this logic, and make everything
// 10 MHz synchronous.
reg phase1, phase2, phase3, a_select;
always @ (posedge clk40 or posedge rst) if (rst) begin
phase1 <= 1'b0;
phase2 <= 1'b0;
phase3 <= 1'b0;
a_select <= 1'b0;
end else begin
phase1 <= quad_sync;
phase2 <= phase1;
phase3 <= phase2;
// of course, because all these quantities are registered,
// a_select ends up high during phase2 and phase3.
a_select <= phase1 | phase2;
end
// States: state_advance: if (state_advance) reload:
// 0 8.437ms Wait for trigger rf t2
// 1 1.434ms Idle t2==0 t1
// 2 0.001ms Idle 1 t2
// 3 0.030ms ADC warmup | t2==0 t2
// 4 0.020ms Feedforward only !rf | t2==0 t2
// 5 1.015ms Feedback !rf | t2==0 t2
// 6 0.030ms Acquire decay curve t2==0
// 7 5.700ms Inhibit retrigger t1==0 t1
//
// But: if ignore_rf_off is set, states 1, 3, 4, and 5 continue to timer
// expiration even when rf is false.
// And if self_retrigger is set, advance out of state 0 if t1==0.
//
// In a live system, both those command flags would be cleared.
// If you only have a short trigger pulse, that is low by the time
// rf should be turned on, set ignore_rf_off. For triggerless bench
// testing, set both ignore_rf_off and self_retrigger.
// The state register is used so much, I choose a single letter for
// its Verilog name: "s". This keeps the lines shorter.
reg [2:0] s;
reg [17:0] t1;
reg [15:0] t2;
reg [15:0] ttable[7:0];
reg [2:0] host_address_hold;
reg old_rf;
reg cw_op;
wire ext_trigger, timer_zero, timer_inhibit, rf_kill_advance, start_cycle, state_advance;
assign ext_trigger = rf & ~old_rf;
assign timer_zero = (s==0 | s==7) ? t1==0 : t2==0;
assign timer_inhibit = (s==0) & ~self_retrigger;
assign rf_kill_advance = (s==4 | s==5) & ~rf & ~ignore_rf_off;
assign start_cycle = (s==0) & ext_trigger;
assign state_advance = start_cycle | (timer_zero & !timer_inhibit) |
(s==2) | rf_kill_advance;
wire t1_reload, t2_reload;
assign t1_reload = halt | state_advance & (s==1 | s==7);
assign t2_reload = state_advance & (s==0 | s==2 | s==3 | s==4 | s==5);
// The RAM address is multiplexed between host R/W, and
// making the output available for time reloading.
wire [2:0] ta;
assign ta = a_select ? host_address_hold : s;
reg table_write;
always @ (posedge clk40 or posedge rst) if (rst) begin
old_rf <= 1'b0;
s <= 3'b0;
t1 <= 18'b0;
t2 <= 16'b0;
trigger_skipped <= 1'b0;
end else if (phase1) begin
old_rf <= rf;
trigger_skipped <= (s!=0) & ext_trigger;
if (state_advance | halt) s <= halt ? 0 : s+1;
t1 <= t1_reload ? {ttable[ta],2'b00} : t1-1;
t2 <= t2_reload ? ttable[ta] : t2-1;
end
always @ (posedge clk40 or posedge rst) if (rst) begin
host_read <= 16'b0;
end else if (phase3) begin
host_read <= ttable[ta];
end
// Can't reset a register file
always @ (posedge clk40) if (phase3 & table_write) begin
$display("table[%d] replaced %x with %x",ta,ttable[ta],host_data);
ttable[ta] <= host_data;
end
always @ (posedge clk40 or posedge rst) if (rst) begin
table_write <= 1'b0;
host_address_hold <= 3'b0;
end else begin
if (phase3 | write_pulse) table_write <= write_pulse;
if (~table_write) host_address_hold <= host_address_hold1;
end
// It's OK to register all of these, and delay everything 100 ns.
// That amount of time shift for any one signal is probably irrelevant,
// and registering them all balances the delays and guarantees no glitches.
always @ (posedge clk40 or posedge rst) if (rst) begin
adc_power <= 1'b0;
feedback_run <= 1'b0;
integrate_run <= 1'b0;
feedforward_start <= 1'b0;
trace_run <= 1'b0;
host_interrupt <= 1'b0;
cw_op <= 1'b0;
end else if (phase1) begin
adc_power <= s==3 | s==4 | s==5 | s==6 | cw_op;
feedback_run <= s==5 | cw_op;
integrate_run <= s==4 | s==5 | cw_op;
feedforward_start <= s==3 & t2==0 & ~cw_op;
trace_run <= s==4 | s==5 | s==6;
host_interrupt <= s==6 & t2==0;
if (s==4 & t2==0 ) cw_op <= cw_request;
end
reg [1:0] totalizer_mode;
reg [11:0] totalizer_counter;
wire totalizer_mode_step = ~totalizer_mode[1] ?
( ~totalizer_mode[0] ? s==3 : totalizer_counter==0 ) :
( ~totalizer_mode[0] ? totalizer_counter==0 : s==2 ) ;
wire [11:0] totalizer_load = ~totalizer_mode[0] ? {totalizer_delay[7:0],{4{1'b0}}} : 12'h100 ;
always @(posedge clk40 or posedge rst) if (rst) begin
totalizer_mode <= 2'b0;
totalizer_gate <= 1'b0;
totalizer_counter <= 12'b0;
end else begin
if (phase1 & totalizer_mode_step) totalizer_mode <= totalizer_mode + 1'b1;
if (phase1 & (totalizer_mode_step | (totalizer_mode==1) | (totalizer_mode==2)))
totalizer_counter <=
totalizer_mode_step ? totalizer_load : totalizer_counter-1;
totalizer_gate <= totalizer_mode==2;
end
`ifdef SIMULATE
// Remember, t2 uses units of 100 ns, but t1 uses units of 400ns.
initial begin
// totalizer_delay=8'd13; // 13*1.6 = 20.8 us delay
feedforward_start=0;
s=3'b0;
table_write=0;
ttable[0]=10; // (t2) length of state 1 (idle)
ttable[1]=80; // (t1) time until end of state 7
ttable[2]=10; // (t2) length of state 3 (ADC warmup)
ttable[3]=20; // (t2) length of state 4 (feedforward only)
ttable[4]=45; // (t2) length of state 5 (feedback)
ttable[5]=35; // (t2) length of state 6 (acquire decay curve)
ttable[6]=45; // unused
ttable[7]=55; // (t1) length of state 0 (wait for trigger)
end
`endif
endmodule