From 5909f548d5d2325c907182c54cec3b3269a393b9 Mon Sep 17 00:00:00 2001 From: Peter McGoron Date: Mon, 21 Nov 2022 21:41:50 -0500 Subject: [PATCH] control loop simulator passes lint --- firmware/rtl/control_loop/Makefile | 28 +- firmware/rtl/control_loop/adc_sim.v | 68 +++++ firmware/rtl/control_loop/control_loop.v | 116 ++++---- .../rtl/control_loop/control_loop_cmds.vh | 3 +- firmware/rtl/control_loop/control_loop_math.v | 25 +- .../rtl/control_loop/control_loop_sim.cpp | 258 +++++------------- .../rtl/control_loop/control_loop_sim_top.v | 148 +++++----- firmware/rtl/control_loop/dac_sim.v | 71 +++++ firmware/rtl/spi/spi_master_ss_template.v | 1 + firmware/rtl/spi/spi_slave.v | 1 + 10 files changed, 401 insertions(+), 318 deletions(-) create mode 100644 firmware/rtl/control_loop/adc_sim.v create mode 100644 firmware/rtl/control_loop/dac_sim.v diff --git a/firmware/rtl/control_loop/Makefile b/firmware/rtl/control_loop/Makefile index ceaabfe..39a3b97 100644 --- a/firmware/rtl/control_loop/Makefile +++ b/firmware/rtl/control_loop/Makefile @@ -1,14 +1,17 @@ # Makefile for tests and hardware verification. .PHONY: test clean + +####### Tests ######## + COMMON_CPP = control_loop_math_implementation.cpp COMMON= ${COMMON_CPP} control_loop_math_implementation.h CONSTS_FRAC=43 E_WID=21 -test: obj_dir/Vcontrol_loop_math - obj_dir/Vcontrol_loop_math +test: obj_dir/Vcontrol_loop_sim_top obj_dir/Vcontrol_loop_math + # obj_dir/Vcontrol_loop_math clean: rm -rf obj_dir *.fst @@ -24,3 +27,24 @@ obj_dir/Vcontrol_loop_math.mk: control_loop_math_sim.cpp ${COMMON} \ obj_dir/Vcontrol_loop_math: obj_dir/Vcontrol_loop_math.mk cd obj_dir && make -f Vcontrol_loop_math.mk +obj_dir/Vcontrol_loop_sim_top.mk: control_loop_sim.cpp ${COMMON} \ + adc_sim.v dac_sim.v \ + ../spi/spi_master_ss.v \ + ../spi/spi_slave_no_write.v \ + control_loop_sim_top.v control_loop_sim_top.v + verilator --cc --exe -Wall --trace --trace-fst \ + --top-module control_loop_sim_top \ + -GCONSTS_FRAC=${CONSTS_FRAC} \ + -CFLAGS -DCONSTS_FRAC=${CONSTS_FRAC} \ + -CFLAGS -DE_WID=${E_WID} -I../spi \ + control_loop_sim_top.v control_loop.v control_loop_sim.cpp \ + ${COMMON_CPP} adc_sim.v dac_sim.v ../spi/spi_master_ss.v \ + ../spi/spi_slave_no_read.v ../spi/spi_slave.v +obj_dir/Vcontrol_loop_sim_top: obj_dir/Vcontrol_loop_sim_top.mk control_loop_cmds.h + cd obj_dir && make -f Vcontrol_loop_sim_top.mk + +####### Codegen ######## + +control_loop_cmds.h: control_loop_cmds.vh + echo '#pragma once' > control_loop_cmds.h + sed 's/`define/#define/g; s/`//g' control_loop_cmds.vh >> control_loop_cmds.h diff --git a/firmware/rtl/control_loop/adc_sim.v b/firmware/rtl/control_loop/adc_sim.v new file mode 100644 index 0000000..1806a97 --- /dev/null +++ b/firmware/rtl/control_loop/adc_sim.v @@ -0,0 +1,68 @@ +module adc_sim #( + parameter POLARITY = 1, + parameter PHASE = 0, + parameter WID = 18, + parameter WID_LEN = 5 +) ( + input clk, + + input [WID-1:0] indat, + output reg request, + input fulfilled, + output err, + + output miso, + input sck, + input ss_L +); + +wire ss = !ss_L; +reg ss_raised = 0; +reg fulfilled_raised = 0; + +reg ss_buf_L = 1; +reg [WID-1:0] data = 0; +reg rdy = 0; +wire spi_fin; + +always @ (posedge clk) begin + if (ss && !ss_raised) begin + request <= 1; + ss_raised <= 1; + end else if (ss_raised && !ss) begin + ss_raised <= 0; + ss_buf_L <= 1; + rdy <= 0; + request <= 0; + fulfilled_raised <= 0; + end else if (ss_raised && request && fulfilled && !fulfilled_raised) begin + data <= indat; + fulfilled_raised <= 1; + request <= 0; + end else if (ss_raised && request && !fulfilled && fulfilled_raised) begin + rdy <= 1; + fulfilled_raised <= 0; + ss_buf_L <= 0; + end else if (spi_fin) begin + rdy <= 0; + end +end + +spi_slave_no_read #( + .WID(WID), + .WID_LEN(WID_LEN), + .POLARITY(POLARITY), + .PHASE(PHASE) +) spi ( + .clk(clk), + .sck(sck), + .ss_L(ss_buf_L), + .miso(miso), + .to_master(data), + .finished(spi_fin), + .rdy(rdy), + .err(err) +); + +endmodule +`undefineall diff --git a/firmware/rtl/control_loop/control_loop.v b/firmware/rtl/control_loop/control_loop.v index c3f6724..c7dfc1a 100644 --- a/firmware/rtl/control_loop/control_loop.v +++ b/firmware/rtl/control_loop/control_loop.v @@ -1,5 +1,4 @@ -`include control_loop_cmds.vh -`define ERR_WID (ADC_WID + 1) +`include "control_loop_cmds.vh" module control_loop #( @@ -17,14 +16,10 @@ module control_loop parameter CONSTS_WHOLE = 21, parameter CONSTS_FRAC = 43, + parameter CONSTS_SIZ = 7, `define CONSTS_WID (CONSTS_WHOLE + CONSTS_FRAC) parameter DELAY_WID = 16, - /* [ERR_WID_SIZ-1:0] must be able to store - * ERR_WID (= ADC_WID + 1). - */ - parameter ERR_WID_SIZ = 6, `define DATA_WID `CONSTS_WID -`define E_WID (ADC_WID + 1) parameter READ_DAC_DELAY = 5, parameter CYCLE_COUNT_WID = 18, parameter DAC_WID = 24, @@ -34,6 +29,7 @@ module control_loop */ parameter DAC_WID_SIZ = 5, parameter DAC_DATA_WID = 20, +`define E_WID (DAC_DATA_WID + 1) parameter DAC_POLARITY = 0, parameter DAC_PHASE = 1, parameter DAC_CYCLE_HALF_WAIT = 10, @@ -49,13 +45,13 @@ module control_loop output dac_sck, input adc_miso, - output adc_conv, + output adc_conv_L, output adc_sck, /* Hacky ad-hoc read-write interface. */ - input reg [CONTROL_LOOP_CMD_WIDTH-1:0] cmd, - input reg [DATA_WIDTH-1:0] word_in, - output reg [DATA_WIDTH-1:0] word_out, + input reg [`CONTROL_LOOP_CMD_WIDTH-1:0] cmd, + input reg [`DATA_WID-1:0] word_in, + output reg [`DATA_WID-1:0] word_out, input start_cmd, output reg finish_cmd ); @@ -64,8 +60,13 @@ module control_loop reg dac_arm; reg dac_finished; +reg dac_ss = 0; +assign dac_ss_L = !dac_ss; + reg [DAC_WID-1:0] to_dac; +/* verilator lint_off UNUSED */ wire [DAC_WID-1:0] from_dac; +/* verilator lint_on UNUSED */ spi_master_ss #( .WID(DAC_WID), .WID_LEN(DAC_WID_SIZ), @@ -77,7 +78,6 @@ spi_master_ss #( .SS_WAIT_TIMER_LEN(DAC_SS_WAIT_SIZ) ) dac_master ( .clk(clk), - .arm(dac_arm), .mosi(dac_mosi), .miso(dac_miso), .sck_wire(dac_sck), @@ -92,7 +92,7 @@ reg adc_arm; reg adc_finished; wire [ADC_WID-1:0] measured_value; -localparam [3-1:0] DAC_REGISTER = 3b'001; +localparam [3-1:0] DAC_REGISTER = 3'b001; spi_master_ss_no_write #( .WID(ADC_WID), @@ -109,7 +109,7 @@ spi_master_ss_no_write #( .from_slave(measured_value), .miso(adc_miso), .sck_wire(adc_sck), - .ss_L(!ss_conv), + .ss_L(adc_conv_L), .finished(adc_finished) ); @@ -143,12 +143,13 @@ reg running = 0; reg signed [DAC_DATA_WID-1:0] stored_dac_val = 0; reg [CYCLE_COUNT_WID-1:0] last_timer = 0; -reg [CYCLE_COUNT_WID-1:0] debug_timer = 0; +reg [CYCLE_COUNT_WID-1:0] counting_timer = 0; reg [`CONSTS_WID-1:0] adjval_prev = 0; reg signed [`E_WID-1:0] err_prev = 0; -wire signed [`E_WID-1:0] e_cur = 0; -wire signed [`CONSTS_WID-1:0] adj_val = 0; +wire signed [`E_WID-1:0] e_cur; +wire signed [`CONSTS_WID-1:0] adj_val; +wire signed [DAC_DATA_WID-1:0] new_dac_val; reg arm_math = 0; reg math_finished = 0; @@ -157,7 +158,10 @@ control_loop_math #( .CONSTS_FRAC(CONSTS_FRAC), .CONSTS_SIZ(CONSTS_SIZ), .ADC_WID(ADC_WID), - .CYCLE_COUNT_WID(CYCLE_COUNT_WID) + .DAC_WID(DAC_DATA_WID), + .CYCLE_COUNT_WID(CYCLE_COUNT_WID), + .SEC_PER_CYCLE('b10101011110011000), + .ADC_TO_DAC({32'b01000001100, 32'b01001001101110100101111000110101}) ) math ( .clk(clk), .arm(arm_math), @@ -169,6 +173,8 @@ control_loop_math #( .cycles(last_timer), .e_prev(err_prev), .adjval_prev(adjval_prev), + .stored_dac_val(stored_dac_val), + .new_dac_val(new_dac_val), .e_cur(e_cur), .adj_val(adj_val) ); @@ -208,12 +214,13 @@ control_loop_math #( localparam CYCLE_START = 0; localparam WAIT_ON_ADC = 1; localparam WAIT_ON_MATH = 2; +localparam WAIT_ON_DAC = 6; localparam INIT_READ_FROM_DAC = 3; localparam WAIT_FOR_DAC_READ = 4; localparam WAIT_FOR_DAC_RESPONSE = 5; localparam STATESIZ = 3; -reg [STATESIZ-1:0] state = CYCLE_START; +reg [STATESIZ-1:0] state = INIT_READ_FROM_DAC; reg [DELAY_WID-1:0] timer = 0; @@ -232,85 +239,88 @@ end * the main loop is clearing the dirty bit. */ -wire write_control = state == CYCLE_START; +wire write_control = state == CYCLE_START || !running; reg dirty_bit = 0; always @ (posedge clk) begin if (start_cmd && !finish_cmd) begin case (cmd) - CONTROL_LOOP_NOOP: CONTROL_LOOP_NOOP | CONTROL_LOOP_WRITE_BIT: + `CONTROL_LOOP_NOOP: finish_cmd <= 1; - CONTROL_LOOP_STATUS: begin - word_out[DATA_WID-1:1] <= 0; + `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: + `CONTROL_LOOP_STATUS | `CONTROL_LOOP_WRITE_BIT: if (write_control) begin running <= word_in[0]; finish_cmd <= 1; dirty_bit <= 1; end - CONTROL_LOOP_SETPT: begin - word_out[DATA_WID-1:ADC_WID] <= 0; + `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: + `CONTROL_LOOP_SETPT | `CONTROL_LOOP_WRITE_BIT: if (write_control) begin setpt_buffer <= word_in[ADC_WID-1:0]; finish_cmd <= 1; dirty_bit <= 1; end - CONTROL_LOOP_P: begin + `CONTROL_LOOP_P: begin word_out <= cl_p_reg; finish_cmd <= 1; end - CONTROL_LOOP_P | CONTROL_LOOP_WRITE_BIT: begin + `CONTROL_LOOP_P | `CONTROL_LOOP_WRITE_BIT: begin if (write_control) begin cl_p_reg_buffer <= word_in; finish_cmd <= 1; dirty_bit <= 1; end end - CONTROL_LOOP_I: begin + `CONTROL_LOOP_I: begin word_out <= cl_I_reg; finish_cmd <= 1; end - CONTROL_LOOP_I | CONTROL_LOOP_WRITE_BIT: begin + `CONTROL_LOOP_I | `CONTROL_LOOP_WRITE_BIT: begin if (write_control) begin cl_I_reg_buffer <= word_in; finish_cmd <= 1; dirty_bit <= 1; end end - CONTROL_LOOP_DELAY: begin - word_out[DATA_WID-1:DELAY_WID] <= 0; + `CONTROL_LOOP_DELAY: begin + word_out[`DATA_WID-1:DELAY_WID] <= 0; word_out[DELAY_WID-1:0] <= dely; finish_cmd <= 1; end - CONTROL_LOOP_DELAY | CONTROL_LOOP_WRITE_BIT: begin + `CONTROL_LOOP_DELAY | `CONTROL_LOOP_WRITE_BIT: begin if (write_control) begin dely_buffer <= word_in[DELAY_WID-1:0]; finish_cmd <= 1; dirty_bit <= 1; end end - CONTROL_LOOP_ERR: begin - word_out[DATA_WID-1:ERR_WID] <= 0; - word_out[ERR_WID-1:0] <= err_prev; + `CONTROL_LOOP_ERR: begin + word_out[`DATA_WID-1:`E_WID] <= 0; + word_out[`E_WID-1:0] <= err_prev; finish_cmd <= 1; end - CONTROL_LOOP_Z: begin - word_out[DATA_WID-1:DAC_DATA_WID] <= 0; + `CONTROL_LOOP_Z: begin + word_out[`DATA_WID-1:DAC_DATA_WID] <= 0; word_out[DAC_DATA_WID-1:0] <= stored_dac_val; finish_cmd <= 1; end - CONTROL_LOOP_CYCLES: begin - word_out[DATA_WID-1:CYCLE_COUNT_WID] <= 0; + `CONTROL_LOOP_CYCLES: begin + word_out[`DATA_WID-1:CYCLE_COUNT_WID] <= 0; word_out[CYCLE_COUNT_WID-1:0] <= last_timer; finish_cmd <= 0; end + endcase end else if (!start_cmd) begin finish_cmd <= 0; end @@ -320,7 +330,7 @@ always @ (posedge clk) begin case (state) INIT_READ_FROM_DAC: begin if (running) begin - to_dac <= {1, DAC_REGISTER, 20b'0}; + to_dac <= {1'b1, DAC_REGISTER, 20'b0}; dac_arm <= 1; state <= WAIT_FOR_DAC_READ; end @@ -337,13 +347,13 @@ always @ (posedge clk) begin timer <= timer + 1; end else if (timer == READ_DAC_DELAY) begin dac_arm <= 1; - to_dac <= 24b'0; + to_dac <= 24'b0; timer <= 0; end else if (dac_finished) begin state <= CYCLE_START; dac_ss <= 0; dac_arm <= 0; - stored_dac_val <= from_dac; + stored_dac_val <= from_dac[DAC_DATA_WID-1:0]; end end CYCLE_START: begin @@ -355,10 +365,10 @@ always @ (posedge clk) begin /* On change of constants, previous values are invalidated. */ if (dirty_bit) begin setpt <= setpt_buffer; - dely <= dely_buf; - cl_alpha_reg <= cl_alpha_reg_buffer; + dely <= dely_buffer; + cl_I_reg <= cl_I_reg_buffer; cl_p_reg <= cl_p_reg_buffer; - adj_prev <= 0; + adjval_prev <= 0; err_prev <= 0; dirty_bit <= 0; @@ -377,19 +387,19 @@ always @ (posedge clk) begin WAIT_ON_MATH: if (math_finished) begin arm_math <= 0; dac_arm <= 1; - stored_dac_val <= (stored_dac_val + adj_val[`CONSTS_WID-1:CONSTS_FRAC]); - to_dac <= {0, DAC_REGISTER, (dac_adj_val + adj_val[`CONSTS_WID-1:CONSTS_FRAC]); + stored_dac_val <= new_dac_val; + to_dac <= {1'b0, DAC_REGISTER, new_dac_val}; state <= WAIT_ON_DAC; end WAIT_ON_DAC: if (dac_finished) begin state <= CYCLE_START; - dac_ss <= 0; dac_arm <= 0; - err_prev <= err_cur; - adj_old <= newadj; + err_prev <= e_cur; + adjval_prev <= adj_val; end - end + endcase end endmodule +`undefineall diff --git a/firmware/rtl/control_loop/control_loop_cmds.vh b/firmware/rtl/control_loop/control_loop_cmds.vh index 8785f7e..02b0b07 100644 --- a/firmware/rtl/control_loop/control_loop_cmds.vh +++ b/firmware/rtl/control_loop/control_loop_cmds.vh @@ -6,5 +6,6 @@ `define CONTROL_LOOP_ERR 5 `define CONTROL_LOOP_Z 6 `define CONTROL_LOOP_CYCLES 7 -`define CONTROL_LOOP_WRITE_BIT (1 << (CONTROL_LOOP_CMD_WIDTH-1)) +`define CONTROL_LOOP_DELAY 8 `define CONTROL_LOOP_CMD_WIDTH 8 +`define CONTROL_LOOP_WRITE_BIT (1 << (`CONTROL_LOOP_CMD_WIDTH-1)) diff --git a/firmware/rtl/control_loop/control_loop_math.v b/firmware/rtl/control_loop/control_loop_math.v index 2eaee9b..bb74c16 100644 --- a/firmware/rtl/control_loop/control_loop_math.v +++ b/firmware/rtl/control_loop/control_loop_math.v @@ -33,7 +33,7 @@ module control_loop_math #( /* The conversion between the ADC bit (20/2**18) and DAC bit (20.48/2**20) * is 0.256. */ - parameter logic [`CONSTS_WID-1:0] ADC_TO_DAC = {32'b01000001100, 32'b01001001101110100101111000110101}, + parameter [`CONSTS_WID-1:0] ADC_TO_DAC = 64'b0100000110001001001101110100101111000110101, parameter CYCLE_COUNT_WID = 18, parameter DAC_WID = 20 `define E_WID (DAC_WID + 1) @@ -49,6 +49,7 @@ module control_loop_math #( input signed [CYCLE_COUNT_WID-1:0] cycles, input signed [`E_WID-1:0] e_prev, input signed [`CONSTS_WID-1:0] adjval_prev, + input signed [DAC_WID-1:0] stored_dac_val, `ifdef DEBUG_CONTROL_LOOP_MATH output reg [`CONSTS_WID-1:0] dt_reg, @@ -58,6 +59,7 @@ module control_loop_math #( `endif output reg signed [`E_WID-1:0] e_cur, + output signed [DAC_WID-1:0] new_dac_val, output signed [`CONSTS_WID-1:0] adj_val ); @@ -127,6 +129,18 @@ intsat #( .outp(saturated_add) ); +/************************ + * Safely calculate new DAC value. + ************************/ +reg signed [DAC_WID+1-1:0] add_sat_dac; +intsat #( + .IN_LEN(DAC_WID+1), + .LTRUNC(1) +) dac_saturate ( + .inp(add_sat_dac), + .outp(new_dac_val) +); + localparam WAIT_ON_ARM = 0; localparam CALCULATE_ERR = 9; localparam CALCULATE_DAC_E = 7; @@ -136,6 +150,7 @@ localparam CALCULATE_EPIDT = 3; localparam CALCULATE_EP = 4; localparam CALCULATE_A_PART_1 = 5; localparam CALCULATE_A_PART_2 = 6; +localparam CALCULATE_NEW_DAC_VALUE = 10; localparam WAIT_ON_DISARM = 8; reg [4:0] state = WAIT_ON_ARM; @@ -236,8 +251,14 @@ always @ (posedge clk) begin end CALCULATE_A_PART_2: begin add_sat <= tmpstore; + state <= CALCULATE_NEW_DAC_VALUE; + end + CALCULATE_NEW_DAC_VALUE: begin + add_sat_dac <= saturated_add[CONSTS_FRAC+DAC_WID-1:CONSTS_FRAC] + + stored_dac_val; + adj_val <= saturated_add; state <= WAIT_ON_DISARM; - end + end WAIT_ON_DISARM: begin adj_val <= saturated_add; if (!arm) begin diff --git a/firmware/rtl/control_loop/control_loop_sim.cpp b/firmware/rtl/control_loop/control_loop_sim.cpp index f629ca4..bac4362 100644 --- a/firmware/rtl/control_loop/control_loop_sim.cpp +++ b/firmware/rtl/control_loop/control_loop_sim.cpp @@ -8,205 +8,77 @@ #include #include -#include "Vcontrol_loop.h" +#include "control_loop_math_implementation.h" +#include "control_loop_cmds.h" +#include "Vcontrol_loop_sim_top.h" +using ModType = Vcontrol_loop_sim_top; -Vcontrol_loop *mod; - -/* Very simple simulation of measurement. - * A transfer function defines the mapping from the DAC values - * -2**(20) -> 2**(20)-1 - * to the values -2**(18) -> 2**18 - 1. - * - * The transfer function has Gaussian noise which is added at each - * measurement. - */ - -class Transfer { - std::default_random_engine generator; - std::normal_distribution dist; - double scale; - - double sample() {return scale*dist(rd);} - - public: - Transfer(double scale, double mean, double dev, double m, double b, int seed) - : scale{scale}, dist{mean,dev}, generator{} { - if (seed < 0) { - std::random_device rd; - generator.seed(rd()); - } else { - generator.seed(seed); - } - } - - double val(double x) { - return m*x + b + sample(); - } -}; - -/* Each constant is 48 bits long, with 15 whole bits. -constexpr auto CONSTS_WHOLE_WID = 15; -constexpr auto CONSTS_WID = 48; -constexpr auto CONSTS_FRAC_WID = CONSTS_WID - CONSTS_WHOLE_WID; -constexpr auto CONSTS_FRAC_MASK = (1 << CONSTS_FRAC_WID) - 1; - -constexpr uint64_t fractional_base_conv(uint64_t input) { - /* Fractional base conversion algorithm. - Given an integer in base M (i.e. 10) there is an expansion in base N (i.e. 2): - 0.abcdefgh... = 0.ijklmnop... - where abcdefgh... are in base M and ijklmnop... are in base N. The algorithm - computes the digits in base N. - - Multiply the converted number by N. Then there are new numbers: - A.BCDEFGH... = i.jklmnop... - Since 0.abcdefgh < 1, A.BCDEFGH < N. Therefore - the digit "A" must be a number less than N. Then i = A. Cutting off all the - other digits, - 0.BCDEFGH... = 0.jklmnop... - continue until there are no more digits left. - */ - - /* Calculate the lowest power of 10 greater than input. - This can be done with logarithms, but floating point is not available on - some embedded platforms. This makes the code more portable. - */ - uint64_t pow10 = 1; - while (input / pow10 > 0) - pow10 *= 10; - - uint64_t out = 0; - for (unsigned i = 0; i < CONSTS_FRAC_WID; i++) { - input *= 2; - uint64_t dig = input / pow10, mod = input % pow10; - out = dig | (out << 1); - input = mod; - } - - return out; +uint32_t main_time = 0; +double sc_time_stamp() { + return main_time; } -static int64_t multiply_unity(uint64_t i, int sign) { - if (sign > 0) { - return std::reinterpret_cast(i); - } else { - return std::reinterpret_cast(~i + 1); +ModType *mod; + +static void run_clock() { + for (int i = 0; i < 2; i++) { + mod->clk = !mod->clk; + mod->eval(); + main_time++; } } -constexpr uint64_t SCALE_WHOLE = 12820; -constexpr uint64_t SCALE_FRAC = fractional_base_conv(51282051282); -constexpr uint64_t SCALE_NUM = (SCALE_WHOLE << CONSTS_FRAC_WID) | SCALE_FRAC; - -static int64_t signed_to_fxp(char *s) { - // Skip whitespace. - while (isspace(*c++)); - // Check if number is negative. - int sign = 1; - if (*s == '-') { - pos = -1; - s++; - } - - // Split the number into whole and fractional components. - char *p = strchr(s, '.'); - if (!p) - return multiply_unity(strtoull(s, NULL, 10), sign); - *p = 0; - // s now points to a NUL terminated string with the whole number - // component. - uint64_t whole = strtoull(s, NULL, 10); - - p++; - // p is the start of the fractional component. - uint64_t frac_decimal = strtoull(p, NULL, 10); - uint64_t final = ((whole << CONSTS_FRAC_WID) | fractional_base_conv(frac_decimal, CONSTS_FRAC_WID)) - * SCALE_NUM; - return multiply_unity(final, sign); -} - -static std::string fxp_to_str(int64_t inum, unsigned decdigs) { - std::string s = ""; - uint64_t num; - - if (inum < 0) { - num = std::reinterpret_cast(~inum) + 1; - s.insert(0,1, '-'); - } else { - num = std::reinterpet_cast(num); - } - - s += std::to_string(num >> CONSTS_FRAC_WID); - - int64_t frac = num & CONSTS_FRAC_MASK; - if (frac == 0 || decdigs == 0) - return; - - s += "."; - - /* Applying the algorithm in fractional_base_conv() backwards. */ - while (decdigs > 0 && frac != 0) { - num *= 2; - s += std::to_string(num >> CONSTS_FRAC_WID); - num = num & CONSTS_FRAC_WID; - } - - return s; -} - -static int64_t I_const, dt_const, P_const, setpt; -static unsigned long seed, ; - -static void usage(char *argv0, int code) { - std::cout << argv0 << " -d deviation -m mean -I I -t dt -d delay -s seed -S setpt -P p [+verilator...]" << std::endl; - exit(code); -} - -static void parse_args(int argc, char *argv[]) { - const char *optstring = "I:t:s:P:d:m:h"; - int opt; +static void init(int argc, char **argv) { Verilated::commandArgs(argc, argv); - - while ((opt = getopt(argc, argv, optstring)) != -1) { - switch (opt) { - case 'm': - noise_mean = strtod(optstring, NULL); - break; - case 'd': - dev_mean = strtod(optstring, NULL); - break; - case 'I': - I_const = signed_to_fxp(optarg); - break; - case 't': - dt_const = signed_to_fxp(optarg); - break; - case 'S': - setpt = signed_to_fxp(optarg); - break; - case 's': - seed = strtoul(optarg, NULL, 10); - break; - case 'P': - P_const = strtoul(optarg, NULL, 10); - break; - case 'd': - dely = strtoul(optarg, NULL, 10); - break; - case 'h': - usage(argv[0], 0); - break; - default: - usage(argv[1], 1); - break; - } - } -} - -Vtop *mod; - -int main(int argc, char **argv) { - parse_args(argc, argv); - mod = new Vtop; - + Verilated::traceEverOn(true); + mod = new ModType; mod->clk = 0; } + +static void set_value(V val, unsigned name) { + mod->cmd = CONTROL_LOOP_WRITE_BIT | name; + mod->word_into_loop = val; + mod->start_cmd = 1; + + do { run_clock(); } while (!mod->finish_cmd); + mod->start_cmd = 0; + run_clock(); +} + +int main(int argc, char **argv) { + init(argc, argv); + mod = new ModType; + Transfer func = Transfer{150, 0, 2, 1.1, 10, -1}; + + mod->clk = 0; + + set_value(10000, CONTROL_LOOP_STATUS); + set_value(0b11010111000010100011110101110000101000111, CONTROL_LOOP_P); + set_value((V)12 << CONSTS_FRAC, CONTROL_LOOP_I); + set_value(20, CONTROL_LOOP_DELAY); + set_value(1, CONTROL_LOOP_STATUS); + + for (int tick = 0; tick < 10000; tick++) { + std::cout << tick << std::endl; + run_clock(); + if (mod->request && !mod->fulfilled) { + mod->measured_value = func.val(mod->curset); + mod->fulfilled = 1; + } else if (mod->fulfilled && !mod->request) { + mod->fulfilled = 0; + } + + if (tick == 5000) { + mod->cmd = CONTROL_LOOP_WRITE_BIT | CONTROL_LOOP_P; + mod->word_into_loop = 0b010111000010100011110101110000101000111; + mod->start_cmd = 1; + } + if (mod->finish_cmd) { + mod->start_cmd = 0; + } + } + + mod->final(); + delete mod; + return 0; +} diff --git a/firmware/rtl/control_loop/control_loop_sim_top.v b/firmware/rtl/control_loop/control_loop_sim_top.v index 0556b85..ccf4f1e 100644 --- a/firmware/rtl/control_loop/control_loop_sim_top.v +++ b/firmware/rtl/control_loop/control_loop_sim_top.v @@ -1,117 +1,131 @@ -`include control_loop_cmds.vh +`include "control_loop_cmds.vh" -module top -#( +module control_loop_sim_top #( parameter ADC_WID = 18, - parameter ADC_WID_LEN = 5, + parameter ADC_WID_SIZ = 5, parameter ADC_POLARITY = 1, parameter ADC_PHASE = 0, - parameter ADC_CYCLE_HALF_WAIT = 5, - parameter ADC_TIMER_LEN = 3, parameter DAC_POLARITY = 0, parameter DAC_PHASE = 1, parameter DAC_DATA_WID = 20, parameter DAC_WID = 24, - parameter DAC_WID_LEN = 5, - parameter DAC_CYCLE_HALF_WAIT = 10, - parameter DAC_TIMER_LEN = 4, + parameter DAC_WID_SIZ = 5, - parameter CONSTS_WID = 48, + parameter CONSTS_WHOLE = 21, + parameter CONSTS_FRAC = 43, +`define CONSTS_WID (CONSTS_WHOLE + CONSTS_FRAC) + parameter CONSTS_SIZ = 7, parameter DELAY_WID = 16 )( input clk, - input signed [ADC_WID-1:0] measured_data, - input [DAC_DATA_WID-1:0] dac_in, - output [DAC_DATA_WID-1:0] dac_out, - output dac_input_ready, + output [DAC_DATA_WID-1:0] curset, + output dac_err, - input [CONSTS_WID-1:0] word_into_loop, - output [CONSTS_WID-1:0] word_outof_loop, + input [ADC_WID-1:0] measured_value, + output request, + input fulfilled, + output adc_err, + + input [`CONSTS_WID-1:0] word_into_loop, + output [`CONSTS_WID-1:0] word_outof_loop, input start_cmd, output finish_cmd, - input [CONTROL_LOOP_CMD_WIDTH-1:0] cmd + input [`CONTROL_LOOP_CMD_WIDTH-1:0] cmd ); -wire dac_miso; -wire dac_mosi; -wire dac_sck; -wire ss_L; - -spi_master #( - .WID(DAC_WID), - .WID_LEN(DAC_WID_LEN), - .POLARITY(DAC_POLARITY), - .PHASE(DAC_PHASE), - .CYCLE_HALF_WAIT(DAC_CYCLE_HALF_WAIT), - .TIMER_LEN(DAC_TIMER_LEN) -) dac_master ( - .clk(clk), - .from_slave(dac_set_data), - .miso(dac_miso), - .to_slave( - .mosi(dac_mosi), - -wire adc_sck; -wire adc_ss; -wire adc_miso; -reg adc_finished = 0; - -wire dac_mosi; -wire dac_sck; -wire dac_ss; -reg dac_finished = 0; - /* Emulate a control loop environment with simulator controlled SPI interfaces. */ +wire adc_miso; +wire adc_sck; +wire adc_ss_L; + /* ADC */ -spi_slave_no_write #( + +adc_sim #( .WID(ADC_WID), .WID_LEN(5), - .ADC_POLARITY(ADC_POLARITY), - .ADC_PHASE(ADC_PHASE) -)( + .POLARITY(ADC_POLARITY), + .PHASE(ADC_PHASE) +) adc ( .clk(clk), - .to_master(measured_data), - .sck(adc_sck), - .ss_L(!adc_ss), + .indat(measured_value), + .request(request), + .fulfilled(fulfilled), + .err(adc_err), + .miso(adc_miso), - .rdy(!dac_ss), - .finished(adc_finished) + .sck(adc_sck), + .ss_L(adc_ss_L) ); +wire dac_miso; +wire dac_mosi; +wire dac_ss_L; +wire dac_sck; + /* DAC */ -spi_slave_no_read #( +dac_sim #( .WID(DAC_WID), + .DATA_WID(DAC_DATA_WID), .WID_LEN(5), - .DAC_POLARITY(DAC_POLARITY), - .DAC_PHASE(DAC_PHASE) -)( + .POLARITY(DAC_POLARITY), + .PHASE(DAC_PHASE) +) dac ( .clk(clk), - .from_master(output_data), + .curset(curset), .mosi(dac_mosi), + .miso(dac_miso), .sck(dac_sck), - .ss_L(!dac_ss), - .rdy(!dac_ss), - .finished(dac_finished) + .ss_L(dac_ss_L), + .err(dac_err) ); control_loop #( .ADC_WID(ADC_WID), - .DAC_WID(DAC_WID), - .DAC_DATA_WID(DAC_DATA_WID), - .CONSTS_WID(CONSTS_WID), - .DELAY_WID(DELAY_WID), + .ADC_WID_SIZ(ADC_WID_SIZ), .ADC_POLARITY(ADC_POLARITY), .ADC_PHASE(ADC_PHASE), + /* Keeping cycle half wait and conv wait the same + * since it doesn't matter for this simulation */ + + .CONSTS_WHOLE(CONSTS_WHOLE), + .CONSTS_FRAC(CONSTS_FRAC), + .CONSTS_SIZ(CONSTS_SIZ), + .DELAY_WID(DELAY_WID), + + .DAC_WID(DAC_WID), + .DAC_WID_SIZ(DAC_WID_SIZ), + .DAC_DATA_WID(DAC_DATA_WID), .DAC_POLARITY(DAC_POLARITY), .DAC_PHASE(DAC_PHASE) ) cloop ( .clk(clk), + .dac_mosi(dac_mosi), + .dac_miso(dac_miso), + .dac_ss_L(dac_ss_L), + .dac_sck(dac_sck), + .adc_miso(adc_miso), + .adc_conv_L(adc_ss_L), + .adc_sck(adc_sck), + + .word_in(word_into_loop), + .word_out(word_outof_loop), + .start_cmd(start_cmd), + .finish_cmd(finish_cmd), + .cmd(cmd) ); +`ifdef VERILATOR +initial begin + $dumpfile("control_loop.fst"); + $dumpvars; +end +`endif + endmodule +`undefineall diff --git a/firmware/rtl/control_loop/dac_sim.v b/firmware/rtl/control_loop/dac_sim.v new file mode 100644 index 0000000..f012569 --- /dev/null +++ b/firmware/rtl/control_loop/dac_sim.v @@ -0,0 +1,71 @@ +module dac_sim #( + parameter POLARITY = 0, + parameter PHASE = 1, + parameter WID = 24, + parameter DATA_WID = 20, + parameter WID_LEN = 5 +) ( + input clk, + + output reg [DATA_WID-1:0] curset, + + input mosi, + output miso, + input sck, + input ss_L, + output err +); + +wire [WID-1:0] from_master; +reg [WID-1:0] to_master = 0; +reg rdy = 1; +wire spi_fin; +reg [WID-4-1:0] ctrl_register = 0; + +always @ (posedge clk) begin + if (spi_fin) begin + rdy <= 0; + /* read current value. TODO: lower bit DACs have zero + * padding between register and DAC value. */ + case (from_master[WID-1:WID-4]) + 4'b1001: begin + to_master <= {4'b1001, curset}; + end + 4'b0001: begin + curset <= from_master [DATA_WID-1:0]; + to_master <= 0; + end + 4'b0010: begin + ctrl_register <= to_master[WID-1-4:0]; + to_master <= 0; + end + 4'b1010: begin + to_master <= {4'b1010, ctrl_register}; + end + default: ; + endcase + end else if (!rdy) begin + rdy <= 1; + end +end + +spi_slave #( + .WID(WID), + .WID_LEN(WID_LEN), + .POLARITY(POLARITY), + .PHASE(PHASE) +) spi ( + .clk(clk), + .sck(sck), + .ss_L(ss_L), + .miso(miso), + .mosi(mosi), + .from_master(from_master), + .to_master(to_master), + .finished(spi_fin), + .rdy(rdy), + .err(err) +); + +endmodule +`undefineall diff --git a/firmware/rtl/spi/spi_master_ss_template.v b/firmware/rtl/spi/spi_master_ss_template.v index e2e0cc4..cd24740 100644 --- a/firmware/rtl/spi/spi_master_ss_template.v +++ b/firmware/rtl/spi/spi_master_ss_template.v @@ -106,3 +106,4 @@ always @ (posedge clk) begin end endmodule +`undefineall diff --git a/firmware/rtl/spi/spi_slave.v b/firmware/rtl/spi/spi_slave.v index c4f7a7c..bf7445d 100644 --- a/firmware/rtl/spi/spi_slave.v +++ b/firmware/rtl/spi/spi_slave.v @@ -146,3 +146,4 @@ always @ (posedge clk) begin end endmodule +`undefineall