upsilon/firmware/rtl/control_loop/control_loop_math.v

282 lines
6.7 KiB
Coq
Raw Normal View History

2022-10-28 17:31:23 -04:00
/*************** Precision **************
* The control loop is designed around these values, but generally
* does not hardcode them.
*
* Since α and P are precalculated outside of the loop, their
* conversion to numbers the loop understands is done outside of
* the loop and in the kernel.
*
* The 18-bit ADC is twos-compliment, -10.24V to 10.24V,
* with 78μV per increment.
* The 20-bit DAC is twos-compliment, -10V to 10V.
*
* The `P` constant has a minimum value of 1e-7 with a precision
* of 1e-9, and a maxmimum value of 1.
*
* The `I` constant has a minimum value of 1e-4 with a precision
* of 1e-6 and a maximum value of 100.
*
* Δt is cycles/100MHz. This makes Δt at least 10 ns, with a
* maximum of 1 ms.
*
* [1 : sign][7: whole][40: fractional]
* This is 127 to -128, with a resolution of 9.095e-13.
*/
/* If this design needs to be faster, you can:
1) Pipeline the design
2) Use DSPs
With symbiflow + yosys there is no way to explicitly instantiate
a DSP40 module. YOSYS may infer it but that might be unreliable.
*/
module control_loop_math #(
parameter CONSTS_WHOLE = 8,
parameter CONSTS_FRAC = 40,
`define CONSTS_WID (CONSTS_WHOLE + CONSTS_FRAC)
2022-10-28 17:31:23 -04:00
/* This number is the conversion from ADC voltage units to
* a fixed-point number.
* A micro-optimization could roll the ADC reading and the multiplier
* together.
* The LSB of this number is 2**(-CONSTS_FRAC).
*/
parameter INT_TO_REAL_WID = 27,
parameter [INT_TO_REAL_WID-1:0] INT_TO_REAL = 'b101000111001001111101110010,
/* This number is 1/(clock cycle).
The number is interpreted so the least significant bit
coincides with the LSB of a constant. */
parameter SEC_PER_CYCLE_WID = 14,
parameter [SEC_PER_CYCLE_WID-1:0] SEC_PER_CYCLE = 'b10101011110011,
parameter DELAY_WID = 16,
parameter DAC_DATA_WID = 20,
parameter ADC_WID = 18,
parameter CYCLE_COUNT_WID = 18
) (
input clk,
input arm,
output finished,
input [ADC_WID-1:0] setpt,
input [ADC_WID-1:0] measured,
input [`CONSTS_WID-1:0] cl_P,
input [`CONSTS_WID-1:0] cl_I,
input [`CONSTS_WID-1:0] e_prev,
2022-10-28 17:31:23 -04:00
input [CYCLE_COUNT_WID-1:0] cycles,
input [DELAY_WID-1:0] dely,
output reg [`CONSTS_WID-1:0] e_cur,
2022-10-28 17:31:23 -04:00
output reg [DAC_DATA_WID-1:0] adjval
);
/**** Stage 1: Convert error to real value, calculate Δt = cycles/100MHz
*
* e_unscaled: ERR_WID.0
* x INT_TO_REAL: 0.INT_TO_REAL_WID
*- -----------------------------
* e_scaled_unsat: ERR_WID + INT_TO_REAL_WID
*/
`define ERR_WID (ADC_WID + 1)
wire [`ERR_WID-1:0] e_unscaled = setpt - measured;
2022-10-28 17:31:23 -04:00
reg arm_stage_1 = 0;
wire mul_scale_err_fin;
`define E_UNTRUNC_WID (`ERR_WID + INT_TO_REAL_WID)
wire [`E_UNTRUNC_WID-1:0] e_scaled_unsat;
2022-10-28 17:31:23 -04:00
boothmul #(
.A1_LEN(INT_TO_REAL_WID),
.A2_LEN(`ERR_WID)
2022-10-28 17:31:23 -04:00
) mul_scale_err (
.clk(clk),
.arm(arm_stage_1),
.a1(INT_TO_REAL),
.a2(e_unscaled),
.outn(e_scaled_unsat),
.fin(mul_scale_err_fin)
);
`define E_WID (`E_UNTRUNC_WID > `CONSTS_WID ? `CONSTS_WID : `E_UNTRUNC_WID)
wire [`E_WID-1:0] e;
2022-10-28 17:31:23 -04:00
`define E_FRAC (`E_WID < `CONSTS_FRAC ? `E_WID : `E_WID - `CONSTS_FRAC)
`define E_WHOLE (`E_WID - `E_FRAC)
2022-10-28 17:31:23 -04:00
/* Don't bother keeping numbers larger than the constant width
* since the value will always fit in it. */
generate if (`E_UNTRUNC_WID > `CONSTS_WID) begin
2022-10-28 17:31:23 -04:00
intsat #(
.IN_LEN(`E_UNTRUNC_WID),
.LTRUNC(`E_UNTRUNC_WID - `CONSTS_WHOLE)
2022-10-28 17:31:23 -04:00
) sat_mul_scale_err (
.inp(e_scaled_unsat),
.outp(e)
);
end else begin
assign e = e_scaled_unsat;
end endgenerate
/* cycles: CYCLE_COUNT_WID.0
* SEC_PER_CYCLE: 0....SEC_PER_CYCLE_WID
* -x--------------------------------
* dt_unsat: CYCLE_COUNT_WID + SEC_PER_CYCLE_WID
*
* Optimization note: the total width can be capped to below 1.
*/
`define DT_UNSAT_WID (CYCLE_COUNT_WID + SEC_PER_CYCLE_WID)
wire [`DT_UNSAT_WID-1:0] dt_unsat;
2022-10-28 17:31:23 -04:00
wire mul_dt_fin;
boothmul #(
.A1_LEN(CYCLE_COUNT_WID),
.A2_LEN(SEC_PER_CYCLE_WID)
) mul_dt (
.clk(clk),
.arm(arm_stage_1),
.a1(cycles),
.a2(SEC_PER_CYCLE),
.outn(dt_unsat),
.fin(mul_dt_fin)
);
`define DT_WID (`DT_UNSAT_WID > `CONSTS_WID ? `CONSTS_WID : `DT_UNSAT_WID)
wire [`DT_WID-1:0] dt;
2022-10-28 17:31:23 -04:00
`define DT_WHOLE (`DT_WID < `CONSTS_FRAC ? 0 : `CONSTS_FRAC - `DT_WID)
`define DT_FRAC(`DT_WID - `DT_WHOLE)
2022-10-28 17:31:23 -04:00
generate if (`DT_UNSAT_WID > `CONSTS_WID) begin
2022-10-28 17:31:23 -04:00
intsat #(
.IN_LEN(`DT_UNSAT_WID),
.LTRUNC(`DT_UNSAT_WID - `CONSTS_WID)
2022-10-28 17:31:23 -04:00
) insat_dt (
.inp(dt_unsat),
.outp(dt)
);
end else begin
assign dt = dt_unsat;
end endgenerate
/**** Stage 2: Calculate P + IΔt
* I: CONSTS_WHOLE.CONSTS_FRAC
* x dt: DT_WHOLE.DT_FRAC
*-- -------------------------------
* Idt_unscaled:
*-- --------------------------------
* Idt: CONSTS_WHOLE.CONSTS_FRAC
*
* Right-truncate DT_FRAC bits to ensure CONSTS_FRAC
* Integer-sature the DT_WHOLE bits if it extends far enough
*/
wire stage2_finished;
reg arm_stage2 = 0;
wire [`CONSTS_WID-1:0] idt;
2022-10-28 17:31:23 -04:00
mul_const #(
/* TODO: does this autoinfer CONSTS_WID? */
.CONSTS_WHOLE(CONSTS_WHOLE),
.CONSTS_FRAC(CONSTS_FRAC),
.IN_WHOLE(`DT_WHOLE),
.IN_FRAC(`DT_FRAC)
2022-10-28 17:31:23 -04:00
) mul_const_idt (
.clk(clk),
.inp(dt),
.const_in(cl_I),
.arm(arm_stage2),
.outp(idt),
.finished(stage2_finished)
);
wire [`CONSTS_WID:0] pidt_untrunc = cl_P + idt;
2022-10-28 17:31:23 -04:00
/* Assuming that the constraints on cl_P, I, and dt hold */
wire [`CONSTS_WID-1:0] pidt = pidt_untrunc[`CONSTS_WID-1:0];
2022-10-28 17:31:23 -04:00
/**** Stage 3: calculate e_t(P + IΔt) and P e_{t-1} ****/
reg arm_stage3 = 0;
wire epidt_finished;
wire pe_finished;
wire [`CONSTS_WID-1:0] epidt;
2022-10-28 17:31:23 -04:00
mul_const #(
.CONSTS_WHOLE(`CONSTS_WHOLE),
.CONSTS_FRAC(`CONSTS_FRAC),
.IN_WHOLE(`E_WHOLE),
.IN_FRAC(`E_FRAC)
2022-10-28 17:31:23 -04:00
) mul_const_epidt (
.clk(clk),
.inp(e),
.const_in(idt),
.arm(arm_stage3),
.outp(epidt),
.finished(epidt_finished)
);
wire [`CONSTS_WID-1:0] pe;
2022-10-28 17:31:23 -04:00
mul_const #(
.CONSTS_WHOLE(`CONSTS_WHOLE),
.CONSTS_FRAC(`CONSTS_FRAC),
.IN_WHOLE(`E_WHOLE),
.IN_FRAC(`E_FRAC)
2022-10-28 17:31:23 -04:00
) mul_const_pe (
.clk(clk),
.inp(e),
.const_in(idt),
.arm(arm_stage3),
.outp(pe),
.finished(epidt_finished)
);
/******* State machine ********/
localparam WAIT_ON_ARM = 0;
localparam WAIT_ON_STAGE_1 = 1;
localparam WAIT_ON_STAGE_2 = 2;
localparam WAIT_ON_STAGE_3 = 3;
localparam WAIT_ON_DISARM = 4;
localparam STATE_SIZ = 3;
reg [STATE_SIZ-1:0] state = WAIT_ON_ARM;
always @ (posedge clk) begin
case (state) begin
WAIT_ON_ARM: begin
if (arm) begin
arm_stage_1 <= 1;
state <= WAIT_ON_STAGE_1;
end
end
WAIT_ON_STAGE_1: begin
if (mul_scale_err_fin && mul_dt_fin) begin
arm_stage_1 <= 0;
arm_stage_2 <= 1;
state <= WAIT_ON_STAGE_2;
end
end
WAIT_ON_STAGE_2: begin
if (stage2_finished) begin
arm_stage_2 <= 0;
arm_stage_3 <= 1;
state <= WAIT_ON_STAGE_3;
end
end
WAIT_ON_STAGE_3: begin
if (epidt_finished && pe_finished) begin
arm_stage3 <= 0;
finished <= 1;
state <= WAIT_ON_DISARM;
end
end
WAIT_ON_DISARM: begin
if (!arm) begin
finished <= 0;
state <= WAIT_ON_ARM;
end
end
end
endmodule