From 029cc53c5fbd17939bc10dc0937e516349c7834e Mon Sep 17 00:00:00 2001 From: Peter McGoron Date: Mon, 17 Oct 2022 00:44:30 -0400 Subject: [PATCH] some more changes --- doc/fixedpoint.py | 64 +++++++ firmware/rtl/control_loop/control_loop.v | 11 +- .../rtl/control_loop/control_loop_sim.cpp | 180 +++++++++++++++++- firmware/rtl/control_loop/top.v | 74 ++++++- firmware/rtl/raster/raster.v | 101 ++++++++++ firmware/rtl/sign_extend.v | 19 ++ firmware/rtl/spi/spi_slave.v | 137 +++++++++++++ firmware/rtl/spi/spi_slave_no_write.v | 3 + 8 files changed, 576 insertions(+), 13 deletions(-) create mode 100644 doc/fixedpoint.py create mode 100644 firmware/rtl/raster/raster.v create mode 100644 firmware/rtl/sign_extend.v create mode 100644 firmware/rtl/spi/spi_slave.v create mode 100644 firmware/rtl/spi/spi_slave_no_write.v diff --git a/doc/fixedpoint.py b/doc/fixedpoint.py new file mode 100644 index 0000000..db8c472 --- /dev/null +++ b/doc/fixedpoint.py @@ -0,0 +1,64 @@ +# Functions for converting to and from fixed point in Python. +from math import log10, floor + +def string_to_fixed_point(s, fracnum): + l = s.split('.') + if len(l) == 1: + return int(s) << fracnum + elif len(l) != 2: + return None + + dec = 10 + frac = 0 + + frac_decimal = int(l[1]) + # get the smallest power of ten higher then frac_decimal + frac_decimal_len = floor(log10(frac_decimal) + 1) + pow10 = 10**frac_decimal_len + frac = 0 + + # Example: + # 0.4567 = 0.abcdefgh... + # where abcdefgh are binary digits. + # multiply both sides by two: + # 0.9134 = a.bcdefgh ... + # therefore a = 0. Then remove the most significant digit. + # Then multiply by 2 again. Then + # 18268 = b.cdefgh ... + # therefore b = 1. Then take 8268, and so on. + for i in range(0,fracnum): + frac_decimal = frac_decimal * 2 + div, mod = divmod(frac_decimal, pow10) + frac = div | (frac << 1) + frac_decimal = mod + + whole = int(l[0]) + if whole < 0: + return -((-whole) << fracnum | frac) + else: + return whole << fracnum | frac + +def fixed_point_to_string(fxp, fracnum): + whole = str(fxp >> fracnum) + mask = (1 << fracnum) - 1 + fracbit = fxp & mask + n = 1 + frac = "" + + if fracbit == 0: + return whole + + # The same method can be applied backwards. + # 0.1110101 = 0.abcdefgh ... + # where abcdefgh... are decimal digits. Then multiply by 10 to + # get + # 1001.0010010 = a.bcdefgh ... + # therefore a = 0b1001 = 9. Then use a bitmask to get + # 0.0010010 = 0.bcdefgh ... + # etc. + + for i in range(0, fracnum): + fracbit = fracbit * 10 + frac = frac + str(fracbit >> fracnum) + fracbit = fracbit & mask + return whole + "." + frac diff --git a/firmware/rtl/control_loop/control_loop.v b/firmware/rtl/control_loop/control_loop.v index cbef1b8..b8b2d8b 100644 --- a/firmware/rtl/control_loop/control_loop.v +++ b/firmware/rtl/control_loop/control_loop.v @@ -1,3 +1,5 @@ +/* TODO: move SPI masters out of the control loop design */ + /************ Introduction to PI Controllers * The continuous form of a PI loop is * @@ -129,16 +131,17 @@ module control_loop output adc_sck, input adc_in, - output adc_conv, + output adc_conv, // active high output dac_sck, - output dac_ss, + output dac_ss, // active high output dac_out, /* Informational output. * These registers are also used for storing information while * the loop is running. */ + output signed [ERR_WID-1:0] err_cur, output signed [CONSTS_WID-1:0] adj, @@ -375,20 +378,24 @@ always @ (posedge clk) begin state <= WAIT_ON_ADC; timer <= 0; adc_arm <= 1; + adc_conv <= 1; end end WAIT_ON_ADC: if (adc_finished) begin adc_arm <= 0; + adc_conv <= 0; arm_mul <= 1; state <= WAIT_ON_MUL; end WAIT_ON_MUL: if (mul_finished) begin arm_mul <= 0; dac_arm <= 1; + dac_ss <= 1; state <= WAIT_ON_DAC; end WAIT_ON_DAC: if (dac_finished) begin state <= WAIT_ON_ARM; + dac_ss <= 0; dac_arm <= 0; end end diff --git a/firmware/rtl/control_loop/control_loop_sim.cpp b/firmware/rtl/control_loop/control_loop_sim.cpp index a764900..7aa5efb 100644 --- a/firmware/rtl/control_loop/control_loop_sim.cpp +++ b/firmware/rtl/control_loop/control_loop_sim.cpp @@ -1,8 +1,11 @@ #include #include #include +#include +#include #include #include +#include #include #include "Vcontrol_loop.h" @@ -19,21 +22,190 @@ Vcontrol_loop *mod; */ class Transfer { - std::random_device rd; + 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) - : scale{scale}, rd{}, dist{mean,dev} {} + 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(); } }; -int main(int argc, char **argv) { +/* 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; +} + +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); + } +} + +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 << " -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:h"; + int opt; + Verilated::commandArgs(argc, argv); + + while ((opt = getopt(argc, argv, optstring)) != -1) { + switch (opt) { + 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; + + mod->clk = 0; + mod->arm = 1; + mod->setpt = setpt; + mod->alpha = I_const * dt_const + P_const; + mod->cl_p = P_const; + mod->dy = 5; } diff --git a/firmware/rtl/control_loop/top.v b/firmware/rtl/control_loop/top.v index 6c516ff..6b68a4c 100644 --- a/firmware/rtl/control_loop/top.v +++ b/firmware/rtl/control_loop/top.v @@ -7,10 +7,12 @@ module top parameter DAC_DATA_WID = 20, parameter CONSTS_WID = 48, parameter DELAY_WID = 16 -( +)( input clk, - input signed [ADC_WID-1:0] read_data, - output signed [DAC_WID-1:0] write_data, + input arm, + + input signed [ADC_WID-1:0] measured_data, + output signed [DAC_WID-1:0] output_data, input signed [ADC_WID-1:0] setpt, input signed [CONSTS_WID-1:0] alpha, input signed [CONSTS_WID-1:0] cl_p, @@ -22,21 +24,79 @@ module top wire adc_sck; wire adc_ss; -wire adc_mosi; +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. + */ + +/* ADC */ spi_slave_no_write #( .WID(ADC_WID), - .WID(5), - . + .WID_LEN(5), + .ADC_POLARITY(ADC_POLARITY), + .ADC_PHASE(ADC_PHASE) +)( + .clk(clk), + .to_master(measured_data), + .sck(adc_sck), + .ss_L(!adc_ss), + .miso(adc_miso), + .rdy(!dac_ss), + .finished(adc_finished) +); + +/* DAC */ +spi_slave_no_read #( + .WID(DAC_WID), + .WID_LEN(5), + .DAC_POLARITY(DAC_POLARITY), + .DAC_PHASE(DAC_PHASE) +)( + .clk(clk), + .from_master(output_data), + .mosi(dac_mosi), + .sck(dac_sck), + .ss_L(!dac_ss), + .rdy(!dac_ss), + .finished(dac_finished) +); control_loop #( .ADC_WID(ADC_WID), .DAC_WID(DAC_WID), .DAC_DATA_WID(DAC_DATA_WID), .CONSTS_WID(CONSTS_WID), - .DELAY_WID(DELAY_WID) + .DELAY_WID(DELAY_WID), + .ADC_POLARITY(ADC_POLARITY), + .ADC_PHASE(ADC_PHASE), + .DAC_POLARITY(DAC_POLARITY), + .DAC_PHASE(DAC_PHASE) ) cloop ( + .clk(clk), + .arm(arm), + .adc_sck(adc_sck), + .adc_in(adc_miso), + .adc_conv(adc_ss), + + .dac_sck(dac_sck), + .dac_ss(dac_ss), + .dac_out(dac_mosi), + + .setpt_in(setpt), + .cl_alpha_in(alpha), + .cl_p_in(cl), + .delay_in(dy), + + .err(err_cur), + .adj(adj) ); endmodule diff --git a/firmware/rtl/raster/raster.v b/firmware/rtl/raster/raster.v new file mode 100644 index 0000000..73fe1c9 --- /dev/null +++ b/firmware/rtl/raster/raster.v @@ -0,0 +1,101 @@ +module raster #( + parameter SAMPLEWID = 9, + parameter DAC_DATA_WID = 20, + parameter DAC_WID = 24, + parameter STEPWID = 16, + parameter MAX_ADC_DATA_WID = 24 +) ( + input clk, + input arm, + + /* Amount of steps per sample. */ + input [STEPWID-1:0] steps, + /* Amount of samples in one line (forward) */ + input [SAMPLEWID-1:0] samples, + /* Amount of lines in the output. */ + input [SAMPLEWID-1:0] lines, + + /* Each step goes (x,y) -> (dx,dy) forward for each line of + * the output. */ + input signed [DAC_DATA_WID-1:0] dx, + input signed [DAC_DATA_WID-1:0] dy, + + /* Vertical steps to go to the next line. */ + input signed [DAC_DATA_WID-1:0] dx_vert, + input signed [DAC_DATA_WID-1:0] dy_vert, + + /* X and Y DAC piezos */ + input x_ready, + output [DACWID-1:0] x_to_dac, + input [DACWID-1:0] x_from_dac, + output x_finished, + + input y_ready, + output [DACWID-1:0] y_to_dac, + input [DACWID-1:0] y_from_dac, + output y_finished, + + /* Connections to all possible ADCs. These are connected to SPI masters + * and they will automatically extend ADC value lengths to their highest + * values. */ + input adc_in [0:ADCNUM-1], + output [MAX_ADC_DATA_WID-1:0] adc_conv [0:ADCNUM-1], + output adc_finished [0:ADCNUM-1], + + /* Bitmap for which ADCs are used. */ + input [ADCNUM-1:0] adc_used, + + output signed [MAX_ADC_DATA_WID-1:0] fifo_data, + output fifo_ready, + input fifo_valid +); + +/* State machine: + ┏━━━━ WAIT ON ARM + ↑ ↓ (arm -> 1) + ┃ GET DAC VALUES + ┃ ↓ (when x and y values are obtained) + ┃ ┏━LOOP FORWARD WITHOUT MEASUREMENT + ┃ ↑ ↓ (when enough steps are taken) + ┃ ┃ GET ADC VALUES + ┃ ┃ ↓ (when all ADC values are obtained) + ┃ ┃ SEND THROUGH FIFO + ┃ ┃ ↓ (when finished) + ┃ ┏━┫ ┃ + ┃ ↑ ┗━━━←━┫ + ┃ ┃ ┃ (when at the end of a line) + ┃ ┃ ┃ + ┃ ┃ ┏━LOOP BACKWARD WITHOUT MEASUREMENT + ┃ ┃ ↑ ↓ (when enough steps are taken) + ┃ ┃ ┃ GET ADC VALUES, BACKWARDS MEASUREMENT + ┃ ┃ ┃ ↓ (when all ADC values are obtained) + ┃ ┃ ┃ SEND THROUGH FIFO, BACKWARDS MEASUREMENT + ┃ ┃ ┃ ↓ (when finished) + ┃ ┃ ┃ ┃ + ┃ ┃ ┗━━━←━┫ + ┃ ┃ ↓ + ┃ ┗━━━━━━━┫ + ┃ ↓ (when the image is finished) + ┃ ┃ + ┃ WAIT FOR ARM DEASSERT + ┃ ↓ (when arm = 0) + ┗━━━━━━━━━┛ +*/ + +localparam WAIT_ON_ARM = 0; +localparam GET_DAC_VALUES = 1; +localparam INCREMENT_XVAL = 2; +localparam GET_ADC_VAL = 3; +localparam SEND_FIFO = 4; +localparam WAIT_FOR_REARM = 5; + +reg [2:0] stepstate = WAIT_ON_ARM; +reg is_forward = 1; +reg [SAMPLEWID-1:0] samplenum; +reg [STEPWID-1:0] stepnum; + +always @ (posedge clk) begin + +end + +endmodule diff --git a/firmware/rtl/sign_extend.v b/firmware/rtl/sign_extend.v new file mode 100644 index 0000000..02965b7 --- /dev/null +++ b/firmware/rtl/sign_extend.v @@ -0,0 +1,19 @@ +module sign_extend #( + parameter WID1 = 18, + parameter WID2 = 24 +) ( + input signed [WID1-1:0] b1, + output signed [WID2-1:0] b2 +); + +assign b2[WID1-1:0] = b1; +/* Assign the high bits of b2 to be the extension of the + * highest bit of b1. If the MSB of b1 is 1 (i.e. b1 is + * negative), then all high bits of b2 must be negative. + * If the MSB of b1 is 0, then the high bits of b2 must + * be zero. + */ +assign b2[WID2-1:WID1] = {(WID2-WID1){b1[WID1-1]}}; + +endmodule + diff --git a/firmware/rtl/spi/spi_slave.v b/firmware/rtl/spi/spi_slave.v new file mode 100644 index 0000000..6efacdd --- /dev/null +++ b/firmware/rtl/spi/spi_slave.v @@ -0,0 +1,137 @@ +/* (c) Peter McGoron 2022 v0.1 + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v.2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +module +`ifdef SPI_SLAVE_NO_READ +spi_slave_no_read +`elsif SPI_SLAVE_NO_WRITE +spi_slave_no_write +`else +spi_slave +`endif +#( + parameter WID = 24, // Width of bits per transaction. + parameter WID_LEN = 5, // Length in bits required to store WID + parameter POLARITY = 0, + parameter PHASE = 0 // 0 = rising-read falling-write, 1 = rising-write falling-read. +) +( + input clk, + input sck, + input ss_L, +`ifndef SPI_SLAVE_NO_READ + output reg [WID-1:0] from_master, + input reg mosi, +`endif +`ifndef SPI_SLAVE_NO_WRITE + input [WID-1:0] to_master, + output reg miso, +`endif + output reg finished, + input rdy, + output reg err +); + +wire ss = !ss_L; +reg sck_delay = 0; +reg [WID_LEN-1:0] bit_counter = 0; +reg ss_delay = 0; +reg ready_at_start = 0; + +`ifndef SPI_SLAVE_NO_WRITE +reg [WID-1:0] send_buf = 0; +`endif + +task read_data(); +`ifndef SPI_SLAVE_NO_READ + from_master <= from_master << 1; + from_master[0] <= mosi; +`endif +endtask + +task write_data(); +`ifndef SPI_SLAVE_NO_WRITE + send_buf <= send_buf << 1; + miso <= send_buf[WID-1]; +`endif +endtask + +task setup_bits(); +`ifndef SPI_SLAVE_NO_WRITE + /* at Mode 00, the transmission starts with + * a rising edge, and at mode 11, it starts with a falling + * edge. For both modes, these are READs. + * + * For mode 01 and mode 10, the first action is a WRITE. + */ + if (POLARITY == PHASE) begin + miso <= to_master[WID-1]; + send_buf <= to_master << 1; + end else begin + send_buf <= to_master; + end +`endif +endtask + +task check_counter(); + if (bit_counter == WID) begin + err <= ready_at_start; + end else begin + bit_counter <= bit_counter + 1; + end +endtask + +always @ (posedge clk) begin + sck_delay <= sck; + ss_delay <= ss; + + case ({ss_delay, ss}) + 2'b01: begin // rising edge of SS + bit_counter <= 0; + finished <= 0; + err <= 0; + ready_at_start <= rdy; + + setup_bits(); + end + 2'b10: begin // falling edge + finished <= ready_at_start; + end + 2'b11: begin + case ({sck_delay, sck}) + 2'b01: begin // rising edge + if (PHASE == 1) begin + write_data(); + end else begin + read_data(); + end + + if (POLARITY == 0) begin + check_counter(); + end + end + 2'b10: begin // falling edge + if (PHASE == 1) begin + read_data(); + end else begin + write_data(); + end + + if (POLARITY == 1) begin + check_counter(); + end + end + default: ; + endcase + end + 2'b00: if (!rdy) begin + finished <= 0; + err <= 0; + end + endcase +end + +endmodule diff --git a/firmware/rtl/spi/spi_slave_no_write.v b/firmware/rtl/spi/spi_slave_no_write.v new file mode 100644 index 0000000..f60ea0b --- /dev/null +++ b/firmware/rtl/spi/spi_slave_no_write.v @@ -0,0 +1,3 @@ +`define SPI_SLAVE_NO_WRITE +/* verilator lint_off DECLFILENAME */ +`include "spi_slave.v"