some more changes

This commit is contained in:
Peter McGoron 2022-10-17 00:44:30 -04:00
parent 5125719a1f
commit 029cc53c5f
8 changed files with 576 additions and 13 deletions

64
doc/fixedpoint.py Normal file
View File

@ -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

View File

@ -1,3 +1,5 @@
/* TODO: move SPI masters out of the control loop design */
/************ Introduction to PI Controllers /************ Introduction to PI Controllers
* The continuous form of a PI loop is * The continuous form of a PI loop is
* *
@ -129,16 +131,17 @@ module control_loop
output adc_sck, output adc_sck,
input adc_in, input adc_in,
output adc_conv, output adc_conv, // active high
output dac_sck, output dac_sck,
output dac_ss, output dac_ss, // active high
output dac_out, output dac_out,
/* Informational output. /* Informational output.
* These registers are also used for storing information while * These registers are also used for storing information while
* the loop is running. * the loop is running.
*/ */
output signed [ERR_WID-1:0] err_cur, output signed [ERR_WID-1:0] err_cur,
output signed [CONSTS_WID-1:0] adj, output signed [CONSTS_WID-1:0] adj,
@ -375,20 +378,24 @@ always @ (posedge clk) begin
state <= WAIT_ON_ADC; state <= WAIT_ON_ADC;
timer <= 0; timer <= 0;
adc_arm <= 1; adc_arm <= 1;
adc_conv <= 1;
end end
end end
WAIT_ON_ADC: if (adc_finished) begin WAIT_ON_ADC: if (adc_finished) begin
adc_arm <= 0; adc_arm <= 0;
adc_conv <= 0;
arm_mul <= 1; arm_mul <= 1;
state <= WAIT_ON_MUL; state <= WAIT_ON_MUL;
end end
WAIT_ON_MUL: if (mul_finished) begin WAIT_ON_MUL: if (mul_finished) begin
arm_mul <= 0; arm_mul <= 0;
dac_arm <= 1; dac_arm <= 1;
dac_ss <= 1;
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 <= WAIT_ON_ARM;
dac_ss <= 0;
dac_arm <= 0; dac_arm <= 0;
end end
end end

View File

@ -1,8 +1,11 @@
#include <memory> #include <memory>
#include <limits> #include <limits>
#include <cstdint> #include <cstdint>
#include <cstring>
#include <cstdlib>
#include <iostream> #include <iostream>
#include <random> #include <random>
#include <unistd.h>
#include <verilated.h> #include <verilated.h>
#include "Vcontrol_loop.h" #include "Vcontrol_loop.h"
@ -19,21 +22,190 @@ Vcontrol_loop *mod;
*/ */
class Transfer { class Transfer {
std::random_device rd; std::default_random_engine generator;
std::normal_distribution dist; std::normal_distribution dist;
double scale; double scale;
double sample() {return scale*dist(rd);} double sample() {return scale*dist(rd);}
public: public:
Transfer(double scale, double mean, double dev, double m, double b) Transfer(double scale, double mean, double dev, double m, double b, int seed)
: scale{scale}, rd{}, dist{mean,dev} {} : scale{scale}, dist{mean,dev}, generator{} {
if (seed < 0) {
std::random_device rd;
generator.seed(rd());
} else {
generator.seed(seed);
}
}
double val(double x) { double val(double x) {
return m*x + b + sample(); 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<int64_t>(i);
} else {
return std::reinterpret_cast<int64_t>(~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<uint64_t>(~inum) + 1;
s.insert(0,1, '-');
} else {
num = std::reinterpet_cast<uint64_t>(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;
} }

View File

@ -7,10 +7,12 @@ module top
parameter DAC_DATA_WID = 20, parameter DAC_DATA_WID = 20,
parameter CONSTS_WID = 48, parameter CONSTS_WID = 48,
parameter DELAY_WID = 16 parameter DELAY_WID = 16
( )(
input clk, input clk,
input signed [ADC_WID-1:0] read_data, input arm,
output signed [DAC_WID-1:0] write_data,
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 [ADC_WID-1:0] setpt,
input signed [CONSTS_WID-1:0] alpha, input signed [CONSTS_WID-1:0] alpha,
input signed [CONSTS_WID-1:0] cl_p, input signed [CONSTS_WID-1:0] cl_p,
@ -22,21 +24,79 @@ module top
wire adc_sck; wire adc_sck;
wire adc_ss; 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 #( spi_slave_no_write #(
.WID(ADC_WID), .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 #( control_loop #(
.ADC_WID(ADC_WID), .ADC_WID(ADC_WID),
.DAC_WID(DAC_WID), .DAC_WID(DAC_WID),
.DAC_DATA_WID(DAC_DATA_WID), .DAC_DATA_WID(DAC_DATA_WID),
.CONSTS_WID(CONSTS_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 ( ) 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 endmodule

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
`define SPI_SLAVE_NO_WRITE
/* verilator lint_off DECLFILENAME */
`include "spi_slave.v"