add write-read interface to control loop
This commit is contained in:
parent
dc2b1fe339
commit
c42e2fe419
|
@ -1,6 +1,10 @@
|
||||||
/* TODO: standardised access that isn't ad-hoc: wishbone
|
/* TODO: The control loop outputs the adjustment value, not the
|
||||||
* bus */
|
* total value to the DAC. Write code that gets the value from
|
||||||
|
* the DAC and writes the adjusted value to it.
|
||||||
|
*
|
||||||
|
* This can be in another module which only gets the value from
|
||||||
|
* the DAC on reset.
|
||||||
|
*/
|
||||||
/************ Introduction to PI Controllers
|
/************ Introduction to PI Controllers
|
||||||
* The continuous form of a PI loop is
|
* The continuous form of a PI loop is
|
||||||
*
|
*
|
||||||
|
@ -125,11 +129,10 @@ module control_loop
|
||||||
parameter ADC_POLARITY = 1,
|
parameter ADC_POLARITY = 1,
|
||||||
parameter ADC_PHASE = 0,
|
parameter ADC_PHASE = 0,
|
||||||
parameter DAC_POLARITY = 0,
|
parameter DAC_POLARITY = 0,
|
||||||
parameter DAC_PHASE = 1
|
parameter DAC_PHASE = 1,
|
||||||
|
parameter DATA_WID = CONSTS_WID
|
||||||
) (
|
) (
|
||||||
input clk,
|
input clk,
|
||||||
input arm,
|
|
||||||
output running,
|
|
||||||
|
|
||||||
input signed [ADC_WID-1:0] measured_value,
|
input signed [ADC_WID-1:0] measured_value,
|
||||||
output adc_conv,
|
output adc_conv,
|
||||||
|
@ -137,43 +140,41 @@ module control_loop
|
||||||
input adc_finished,
|
input adc_finished,
|
||||||
|
|
||||||
output signed [DAC_WID-1:0] to_dac,
|
output signed [DAC_WID-1:0] to_dac,
|
||||||
|
input signed [DAC_WID-1:0] from_dac,
|
||||||
output dac_ss,
|
output dac_ss,
|
||||||
output dac_arm,
|
output dac_arm,
|
||||||
input dac_finished
|
input dac_finished,
|
||||||
|
|
||||||
input reg read_err_cur,
|
/* Hacky ad-hoc read-write interface. */
|
||||||
output reg read_err_cur_finished,
|
input reg [CONTROL_LOOP_CMD_WIDTH-1:0] cmd,
|
||||||
output signed [ERR_WID-1:0] err_cur,
|
input reg [DATA_WIDTH-1:0] word_in,
|
||||||
output signed [CONSTS_WID-1:0] adj,
|
output reg [DATA_WIDTH-1:0] word_out,
|
||||||
|
input start_cmd,
|
||||||
input signed [ADC_WID-1:0] setpt_in,
|
output reg finish_cmd
|
||||||
input signed [CONSTS_WID-1:0] cl_alpha_in,
|
|
||||||
input signed [CONSTS_WID-1:0] cl_p_in,
|
|
||||||
input [DELAY_WID-1:0] delay_in
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Registers used to lock in values at the start of each iteration */
|
|
||||||
reg signed [ADC_WID-1:0] setpt = 0;
|
reg signed [ADC_WID-1:0] setpt = 0;
|
||||||
reg signed [CONSTS_WID-1:0] cl_alpha_reg = 0;
|
reg signed [CONSTS_WID-1:0] cl_alpha_reg = 0;
|
||||||
reg signed [CONSTS_WID-1:0] cl_p_reg = 0;
|
reg signed [CONSTS_WID-1:0] cl_p_reg = 0;
|
||||||
reg [DELAY_WID-1:0] saved_delay = 0;
|
reg [DELAY_WID-1:0] saved_delay = 0;
|
||||||
|
reg running = 0;
|
||||||
|
|
||||||
/* Registers for PI calculations */
|
/* Registers for PI calculations */
|
||||||
reg signed [ERR_WID-1:0] err_prev = 0;
|
reg signed [ERR_WID-1:0] err_prev = 0;
|
||||||
|
|
||||||
/****** State machine
|
/****** State machine
|
||||||
*
|
*
|
||||||
* -> WAIT_ON_ARM -> WAIT_ON_ADC -> WAIT_ON_MUL -\
|
* -> CYCLE_START -> WAIT_ON_ADC -> WAIT_ON_MUL -\
|
||||||
* \------\------------ WAIT_ON_DAC </
|
* \------\------------ WAIT_ON_DAC </
|
||||||
*
|
*
|
||||||
* The loop will stop and reset all stored data if `arm` is not 1 at
|
****** Outline
|
||||||
* the end of the loop.
|
* There are two systems: the read-write interface and the loop.
|
||||||
* The loop stores all data from the input into registers at
|
* The read-write interface allows another module (i.e. the CPU)
|
||||||
* `WAIT_ON_ADC`, so the program can change constants and setpoints
|
* to access and change constants. When a constant is changed the
|
||||||
* on the fly.
|
* loop must reset.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
localparam WAIT_ON_ARM = 0;
|
localparam CYCLE_START = 0;
|
||||||
localparam WAIT_ON_ADC = 1;
|
localparam WAIT_ON_ADC = 1;
|
||||||
localparam WAIT_ON_MUL = 2;
|
localparam WAIT_ON_MUL = 2;
|
||||||
localparam WAIT_ON_DAC = 3;
|
localparam WAIT_ON_DAC = 3;
|
||||||
|
@ -181,28 +182,7 @@ localparam STATESIZ = 2;
|
||||||
|
|
||||||
reg [STATESIZ-1:0] state = WAIT_ON_ARM;
|
reg [STATESIZ-1:0] state = WAIT_ON_ARM;
|
||||||
|
|
||||||
/***** Outline *****
|
/**** Precision Propogation
|
||||||
* The loop will only iterate when armed. If it is running and `arm`
|
|
||||||
* is deasserted, then it will complete the iteration it is in and
|
|
||||||
* stop.
|
|
||||||
*
|
|
||||||
* At the start of each loop, the constants are read into registers,
|
|
||||||
* so the constants can change while the loop is running.
|
|
||||||
*
|
|
||||||
* First the loop reads from the ADC, and then computes the error
|
|
||||||
* from the setpoint. The setpoint is specified in the same units
|
|
||||||
* as the ADC.
|
|
||||||
*
|
|
||||||
* Afterwards the loop computes the multiplications in the PI loop.
|
|
||||||
* This changes the size of the values in the loop.
|
|
||||||
*
|
|
||||||
* Combinatorially, the new adjusted value is calculated. The original
|
|
||||||
* value has to be stored in the same width as the multiplied values.
|
|
||||||
*
|
|
||||||
* Then the value is truncated to the width of the DAC, with saturation
|
|
||||||
* if necessary, and written out to the DAC.
|
|
||||||
*
|
|
||||||
**** Precision Propogation
|
|
||||||
*
|
*
|
||||||
* Measured value: ADC_WID.0
|
* Measured value: ADC_WID.0
|
||||||
* Setpoint: ADC_WID.0
|
* Setpoint: ADC_WID.0
|
||||||
|
@ -222,7 +202,7 @@ reg [STATESIZ-1:0] state = WAIT_ON_ARM;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**** Calculate Error ****/
|
/**** Calculate Error ****/
|
||||||
assign err_cur = measured_value - setpoint;
|
wire [ERR_WID-1:0] err_cur = measured_value - setpoint;
|
||||||
|
|
||||||
/****** Multiplication *******
|
/****** Multiplication *******
|
||||||
* Truncation of a fixed-point integer to a smaller buffer requires
|
* Truncation of a fixed-point integer to a smaller buffer requires
|
||||||
|
@ -284,7 +264,7 @@ localparam SUB_WHOLE_WID = MUL_WHOLE_WID + 1;
|
||||||
localparam SUB_FRAC_WID = MUL_FRAC_WID;
|
localparam SUB_FRAC_WID = MUL_FRAC_WID;
|
||||||
localparam SUB_WID = SUB_WHOLE_WID + SUB_FRAC_WID;
|
localparam SUB_WID = SUB_WHOLE_WID + SUB_FRAC_WID;
|
||||||
|
|
||||||
reg signed [SUB_WID-1:0] adj_old;
|
reg signed [SUB_WID-1:0] adj_old = 0;
|
||||||
wire signed [SUB_WID-1:0] newadj = adj_old + alpha_err - p_err_prev;
|
wire signed [SUB_WID-1:0] newadj = adj_old + alpha_err - p_err_prev;
|
||||||
|
|
||||||
/**** Discard fractional bits ****
|
/**** Discard fractional bits ****
|
||||||
|
@ -325,27 +305,96 @@ intsat #(
|
||||||
assign to_dac = {4'b0010,dac_adj_val};
|
assign to_dac = {4'b0010,dac_adj_val};
|
||||||
|
|
||||||
reg [DELAY_WID-1:0] timer = 0;
|
reg [DELAY_WID-1:0] timer = 0;
|
||||||
|
/* Reset is asserted when any change happens to the inputs.
|
||||||
|
* It is deasserted when the input pin is deasserted.
|
||||||
|
* This always takes at least 1 cycle so the loop will
|
||||||
|
* always halt.
|
||||||
|
*/
|
||||||
|
reg reset_from_input = 0;
|
||||||
|
|
||||||
always @ (posedge clk) begin
|
always @ (posedge clk) begin
|
||||||
case (state)
|
if (start_cmd && !finish_cmd) begin
|
||||||
WAIT_ON_ARM: begin
|
case (cmd)
|
||||||
if (!arm) begin
|
CONTROL_LOOP_NOOP: CONTROL_LOOP_NOOP | CONTROL_LOOP_WRITE_BIT:
|
||||||
|
finish_cmd <= 1;
|
||||||
|
CONTROL_LOOP_STATUS: begin
|
||||||
|
word_out[DATA_WID-1:1] <= 0;
|
||||||
|
word_out[0] <= running;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
CONTROL_LOOP_STATUS | CONTROL_LOOP_WRITE_BIT:
|
||||||
|
running <= word_in[0];
|
||||||
|
finish_cmd <= 1;
|
||||||
|
reset_from_input <= 1;
|
||||||
|
CONTROL_LOOP_SETPT: begin
|
||||||
|
word_out[DATA_WID-1:ADC_WID] <= 0;
|
||||||
|
word_out[ADC_WID-1:0] <= setpt;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
CONTROL_LOOP_SETPT | CONTROL_LOOP_WRITE_BIT:
|
||||||
|
setpt <= word_in[ADC_WID-1:0];
|
||||||
|
reset_from_input <= 1;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
CONTROL_LOOP_P: begin
|
||||||
|
word_out <= cl_p_reg;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
CONTROL_LOOP_P | CONTROL_LOOP_WRITE_BIT: begin
|
||||||
|
cl_p_reg <= word_in;
|
||||||
|
reset_from_input <= 1;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
CONTROL_LOOP_ALPHA: begin
|
||||||
|
word_out <= cl_alpha_reg;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
CONTROL_LOOP_ALPHA | CONTROL_LOOP_WRITE_BIT: begin
|
||||||
|
cl_alpha_reg <= word_in;
|
||||||
|
reset_from_input <= 1;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
CONTROL_LOOP_DELAY: begin
|
||||||
|
word_out[DATA_WID-1:DELAY_WID] <= 0;
|
||||||
|
word_out[DELAY_WID-1:0] <= saved_delay;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
CONTROL_LOOP_DELAY | CONTROL_LOOP_WRITE_BIT: begin
|
||||||
|
saved_delay <= word_in[DELAY_WID-1:0];
|
||||||
|
reset_from_input <= 1;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
CONTROL_LOOP_ERR: begin
|
||||||
|
word_out[DATA_WID-1:ERR_WID] <= 0;
|
||||||
|
word_out[ERR_WID-1:0] <= err_prev;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
CONTROL_LOOP_ADJ: begin
|
||||||
|
word_out[DATA_WID-1:DAC_DATA_WID] <= 0;
|
||||||
|
word_out[DAC_DATA_WID-1:0] <= dac_adj_val;
|
||||||
|
finish_cmd <= 1;
|
||||||
|
end
|
||||||
|
end else if (!start_cmd) begin
|
||||||
|
finish_cmd <= 0;
|
||||||
|
reset_from_input <= 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
/* This is not a race condition as long as two variables are
|
||||||
|
* not being assigned at the same time. Instead, the lower
|
||||||
|
* assign block will use the older values (i.e. the upper assign
|
||||||
|
* block only takes effect next clock cycle).
|
||||||
|
*/
|
||||||
|
|
||||||
|
always @ (posedge clk) begin
|
||||||
|
if (reset_from_input) begin
|
||||||
|
state <= WAIT_ON_ARM;
|
||||||
adj_prev <= 0;
|
adj_prev <= 0;
|
||||||
err_prev <= 0;
|
err_prev <= 0;
|
||||||
timer <= 0;
|
timer <= 0;
|
||||||
running <= 0;
|
end else if (running) begin case (state)
|
||||||
end else if (timer == 0) begin
|
CYCLE_START: begin
|
||||||
saved_delay <= delay_in;
|
if (timer < saved_delay) begin
|
||||||
timer <= 1;
|
|
||||||
running <= 1;
|
|
||||||
setpt <= setpt_in;
|
|
||||||
/* TODO: cl_alpha change only when loop is stopped */
|
|
||||||
cl_alpha_reg <= cl_alpha_in;
|
|
||||||
cl_p_reg <= cl_p_in;
|
|
||||||
state <= WAIT_LOOP_DELAY;
|
|
||||||
end else if (timer < saved_delay) begin
|
|
||||||
timer <= timer + 1;
|
timer <= timer + 1;
|
||||||
setpt <= setpt_in;
|
|
||||||
end else begin
|
end else begin
|
||||||
state <= WAIT_ON_ADC;
|
state <= WAIT_ON_ADC;
|
||||||
timer <= 0;
|
timer <= 0;
|
||||||
|
@ -366,9 +415,13 @@ always @ (posedge clk) begin
|
||||||
state <= WAIT_ON_DAC;
|
state <= WAIT_ON_DAC;
|
||||||
end
|
end
|
||||||
WAIT_ON_DAC: if (dac_finished) begin
|
WAIT_ON_DAC: if (dac_finished) begin
|
||||||
state <= WAIT_ON_ARM;
|
state <= CYCLE_START;
|
||||||
dac_ss <= 0;
|
dac_ss <= 0;
|
||||||
dac_arm <= 0;
|
dac_arm <= 0;
|
||||||
|
|
||||||
|
err_prev <= err_cur;
|
||||||
|
adj_old <= newadj;
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
`define CONTROL_LOOP_NOOP 0
|
||||||
|
`define CONTROL_LOOP_STATUS 1
|
||||||
|
`define CONTROL_LOOP_SETPT 2
|
||||||
|
`define CONTROL_LOOP_P 3
|
||||||
|
`define CONTROL_LOOP_ALPHA 4
|
||||||
|
`define CONTROL_LOOP_ERR 5
|
||||||
|
`define CONTROL_LOOP_ADJ 5
|
||||||
|
`define CONTROL_LOOP_WRITE_BIT (1 << (CONTROL_LOOP_CMD_WIDTH-1))
|
||||||
|
`define CONTROL_LOOP_CMD_WIDTH 8
|
Loading…
Reference in New Issue