refactor control loop interface

This commit is contained in:
Peter McGoron 2023-06-28 17:38:41 -04:00
parent 8b8e14bc7f
commit 054609a459
13 changed files with 366 additions and 283 deletions

View File

@ -17,5 +17,8 @@ Read `doc/copying/docker.md` to set up the Docker build environment.
* `doc/`: Documentation.
* `doc/copying`: Licenses.
* `gateware/`: FPGA source.
* `gateware/rtl`: Verilog sources.
* `gateware/rtl/control_loop`: Control loop code.
* `gateware/rtl/spi`: SPI code.
* `linux/`: Software that runs on the controller.
* `opensbi/`: OpenSBI configuration files and source fragments.

View File

@ -7,9 +7,7 @@ source distribution.
__________________________________________________________________________
This manual describes the controller software programming. This guide does not
describe client programming (programs that run on the client and interface with
the controller). It does not describe Verilog: see `verilog_manual.md` for
that.
describe Verilog: see `verilog_manual.md` for that.
# Preqreuisites

220
gateware/mmio_descr.py Normal file
View File

@ -0,0 +1,220 @@
import textwrap
class Descr:
def __init__(self, name, blen, rwperm, num, descr):
"""
:param name: Name of the pin without numerical suffix.
:param blen: Bit length of the pin.
:param doc: Restructured text documentation of the register.
:param num: The amount of registers of the same type.
:param read_only: A string that must be either "read-only" or "write-write".
"""
self.name = name
self.blen = blen
self.doc = textwrap.deindent(descr)
self.num =num
self.read_only = read_only == "read-only"
@classmethod
def from_dict(cls, jsdict, name):
return cls(name, jsdict[name]["len"], jsdict[name]["ro"], jsdict[name]["num"], jsdict[name]["doc"])
def store_to_dict(self, d):
d[self.name = {
"len": self.blen,
"doc": self.doc,
"num": self.num,
"ro": ro
}
registers = [
Descr("adc_sel", 3, "read-write", """\
Select which on-FPGA SPI master controls the ADC.
Valid settings:
* ``0``: ADC is controlled by MMIO registers.
* ``0b10``: ADC is controlled by MMIO registers, but conversion is
disabled. This is used to flush output from an out-of-sync ADC.
* ``0b100``: ADC 0 only. ADC is controlled by control loop."""),
Descr("adc_finished", "read-only", """\
Signals that an ADC master has finished an SPI cycle.
Values:
* ``0``: MMIO master is either not armed or currently in a
SPI transfer.
* ``1``: MMIO master has finished.
This flag is on only when ``adc_arm`` is high. The flag does not
mean that data has been received successfully, only that the master
has finished it's SPI transfer.
"""),
Descr("adc_arm", "read-write", """\
Start a DAC master SPI transfer.
If ``adc_arm`` is raised from and the master is currently not in a SPI
transfer, the SPI master will start an SPI transfer and write data
into ``adc_recv_buf``.
If ``adc_arm`` is raised while the master is in an SPI transfer,
nothing changes.
If ``adc_arm`` is lowered while the master is in an SPI transfer,
nothing changes. The SPI cycle will continue to execute and it will
continue to write data to ``adc_recv_buf``.
If the SPI transfer finishes and ``adc_arm`` is still set to
1, then ``adc_finished`` is raised to 1. If ``adc_arm`` is lowered
in this state, then ``adc_finished`` is lowered.
Linear Technologies ADCs must not have their SPI transfers
interrupted. The transfer can be interrupted by
1. Interrupt the signal physically (i.e. pulling out cable connecting
the FPGA to the ADC)
2. Reset of the ADC master
3. Reset of the FPGA
4. Switching ``adc_sel`` to the control loop
If the ADC is interrupted then it will be in an unknown transfer
state. To recover from an unknown transfer state, set ``adc_sel``
to ``0b10`` and run a SPI transfer cycle. This will run the SPI
clock and flush the ADC buffer. The only other way is to power-cycle
the ADC.
If ``adc_sel`` is not set to 0 then the transfer will proceed
as normal, but no data will be received from the ADC."""),
Descr("adc_recv_buf", "read-only", """\
ADC Master receive buffer.
This buffer is stable if there is no ADC transfer caused by ``adc_arm``
is in process.
This register only changes if an SPI transfer is triggered by the MMIO
registers. SPI transfers by other masters will not affect this register.
buffer."""),
Descr("dac_sel", 2, "read-write", """\
Select which on-FPGA SPI master controls the DAC.
Valid settings:
* ``0``: DAC is controlled by MMIO registers.
* ``0b10``: DAC 0 only. DAC is controlled by control loop."""),
Descr("dac_finished", 1, "read-only", """\
Signals that the DAC master has finished transmitting data.
Values:
* ``0``: MMIO master is either not armed or currently in a
SPI transfer.
* ``1``: MMIO master has finished transmitting.
This flag is on only when ``dac_arm`` is high. The flag does not
mean that data has been received or transmitted successfully, only that
the master has finished it's SPI transfer."""),
Descr("dac_arm", 1, "read-write", """\
Start a DAC master SPI transfer.
If ``dac_arm`` is raised from and the master is currently not in a SPI
transfer, the SPI master reads from the ``dac_send_buf`` register and sends
it over the wire to the DAC, while reading data from the DAC into
``dac_recv_buf``.
If ``dac_arm`` is raised while the master is in an SPI transfer,
nothing changes.
If ``dac_arm`` is lowered while the master is in an SPI transfer,
nothing changes. The SPI cycle will continue to execute and it will
continue to write data to ``dac_recv_buf``.
If the SPI transfer finishes and ``dac_arm`` is still set to
1, then ``dac_finished`` is raised to 1. If ``dac_arm`` is lowered
in this state, then ``dac_finished`` is lowered.
Analog Devices DACs can have their SPI transfers interrupted without
issue. However it is currently not possible to interrupt SPI transfers
in software without resetting the entire device.
If ``dac_sel`` is set to another master then the transfer will proceed
as normal, but no data will be sent to or received from the DAC."""),
Descr("dac_recv_buf", 24, "read-only", """\
DAC master receive buffer.
This buffer is stable if there is no DAC transfer caused by ``dac_arm``
is in process.
This register only changes if an SPI transfer is triggered by the MMIO
registers. SPI transfers by other masters will not affect this register.
buffer."""),
Descr("dac_send_buf, 24, "read-write", """\
DAC master send buffer.
Fill this buffer with a 24 bit Analog Devices DAC command. Updating
this buffer does not start an SPI transfer. To send data to the DAC,
fill this buffer and raise ``dac_arm``.
The DAC copies this buffer into an internal register when writing data.
Modifying this buffer during a transfer does not disrupt an in-process
transfer."""),
Descr("cl_assert_change", 1, "read-write", """\
Flush parameter changes to control loop.
When this bit is raised from low to high, this signals the control
loop that it should read in new values from the MMIO registers.
While the bit is raised high, the control loop will read the constants
at most once.
When this bit is raised from high to low before ``cl_change_made``
is asserted by the control loop, nothing happens."""),
Descr("cl_change_made", 1, "read-only", """\
Signal from the control loop that the parameters have been applied.
This signal goes high only while ``cl_assert_change`` is high. No
change will be applied afterwards while both are high."""),
Descr("cl_in_loop_in", 1, "read-only", """\
This bit is high if the control loop is running."""),
Descr("cl_setpt_in", 18, "read-write", """\
Setpoint of the control loop.
This is a twos-complement number in ADC units.
This is a parameter: see ``cl_assert_change``."""),
Descr("cl_P_in", 64, "read-write", """\
Proportional parameter of the control loop.
This is a twos-complement fixed point number with 21 whole
bits and 43 fractional bits. This is applied to the error
in DAC units.
This is a parameter: see ``cl_assert_change``."""),
Descr("cl_I_in", 64, "read-write", """\
Integral parameter of the control loop.
This is a twos-complement fixed point number with 21 whole
bits and 43 fractional bits. This is applied to the error
in DAC units.
This is a parameter: see ``cl_assert_change``."""),
Descr("cl_delay_in", 16, "read-write", """\
Delay parameter of the loop.
This is an unsigned number denoting the number of cycles
the loop should wait between loop executions.
This is a parameter: see ``cl_assert_change``."""),
Descr("cl_cycle_count", 18, "read-only", """\
Delay parameter of the loop.
This is an unsigned number denoting the number of cycles
the loop should wait between loop executions."""),
Descr("cl_z_pos", 20, "read-only", """\
Control loop DAC Z position.
"""),
Descr("cl_z_measured", 18, "read-only", """\
Control loop ADC Z position.
"""),
]

View File

@ -1,7 +1,6 @@
m4_changequote(`⟨', `⟩')
m4_changecom(⟨/*⟩, ⟨*/⟩)
m4_define(generate_macro, ⟨m4_define(M4_$1, $2)⟩)
m4_include(../control_loop/control_loop_cmds.m4)
/*
Copyright 2023 (C) Peter McGoron
@ -291,14 +290,18 @@ m4_define(CL_DATA_WID, CL_CONSTS_WID)
m4_adc_wires(ADC_TYPE3_WID, 6, ADC_PORTS),
m4_adc_wires(ADC_TYPE3_WID, 7, ADC_PORTS),
output cl_in_loop,
input [M4_CONTROL_LOOP_CMD_WIDTH-1:0] cl_cmd,
input [CL_DATA_WID-1:0] cl_word_in,
output reg [CL_DATA_WID-1:0] cl_word_out,
input cl_start_cmd,
output cl_finish_cmd,
input cl_assert_change,
output cl_change_made,
output [DAC_DATA_WID-1:0] cl_z_report
input cl_run_loop_in,
input [ADC_TYPE1_WID-1:0] cl_setpt_in,
input [CL_DATA_WID-1:0] cl_P_in,
input [CL_DATA_WID-1:0] cl_I_in,
input [CL_DELAY_WID-1:0] cl_delay_in,
output [CYCLE_COUNT_WID-1:0] cl_cycle_count,
output [DAC_DATA_WID-1:0] cl_z_pos,
output [ADC_WID-1:0] cl_z_measured
);
assign set_low = 0;
@ -377,12 +380,16 @@ control_loop #(
.adc_miso(adc_sdo_port_0[2]),
.adc_conv_L(adc_conv_L_port_0[2]),
.adc_sck(adc_sck_port_0[2]),
.cmd(cl_cmd),
.word_in(cl_word_in),
.word_out(cl_word_out),
.start_cmd(cl_start_cmd),
.finish_cmd(cl_finish_cmd),
.z_report(cl_z_report)
.assert_change(cl_assert_change),
.change_made(cl_change_made),
.run_loop_in(cl_run_loop_in),
.setpt_in(cl_setpt_in),
.P_in(cl_P_in),
.I_in(cl_I_in),
.delay_in(cl_delay_in),
.cycle_count(cl_cycle_count),
.z_pos(cl_z_pos),
.z_measured(cl_z_measured)
);
endmodule

View File

@ -38,7 +38,7 @@ obj_dir/Vcontrol_loop_sim_top.mk: control_loop_sim.cpp ${COMMON} \
../spi/spi_master_ss.v \
../spi/spi_slave_no_write.v \
control_loop_sim_top.v control_loop_sim_top.v \
control_loop_cmds.vh control_loop.v \
control_loop.v \
${control_loop_math_verilog}
verilator --cc --exe -Wall --trace --trace-fst \
--top-module control_loop_sim_top \
@ -48,19 +48,13 @@ obj_dir/Vcontrol_loop_sim_top.mk: control_loop_sim.cpp ${COMMON} \
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
obj_dir/Vcontrol_loop_sim_top: obj_dir/Vcontrol_loop_sim_top.mk
cd obj_dir && make -f Vcontrol_loop_sim_top.mk
####### Codegen ########
include ../common.makefile
CODEGEN_FILES=control_loop_cmds.h boothmul_preprocessed.v control_loop_math.v control_loop.v control_loop_cmds.vh
CODEGEN_FILES=boothmul_preprocessed.v control_loop_math.v control_loop.v
codegen: ${CODEGEN_FILES}
control_loop_cmds.vh: control_loop_cmds.m4
m4 -P control_loop_cmds.vh.m4 > control_loop_cmds.vh
control_loop_cmds.h: control_loop_cmds.m4
echo '#pragma once' > control_loop_cmds.h
m4 -P control_loop_cmds.h.m4 >> control_loop_cmds.h
clean:
rm -rf obj_dir *.fst ${CODEGEN_FILES}

View File

@ -27,7 +27,6 @@ module control_loop
parameter CONSTS_SIZ = 7,
m4_define(M4_CONSTS_WID, (CONSTS_WHOLE + CONSTS_FRAC))
parameter DELAY_WID = 16,
m4_define(M4_DATA_WID, M4_CONSTS_WID)
parameter READ_DAC_DELAY = 5,
parameter CYCLE_COUNT_WID = 18,
parameter DAC_WID = 24,
@ -47,7 +46,6 @@ m4_define(M4_E_WID, (DAC_DATA_WID + 1))
) (
input clk,
input rst_L,
output in_loop,
output dac_mosi,
input dac_miso,
@ -58,14 +56,20 @@ m4_define(M4_E_WID, (DAC_DATA_WID + 1))
output adc_conv_L,
output adc_sck,
/* Hacky ad-hoc read-write interface. */
input [M4_CONTROL_LOOP_CMD_WIDTH-1:0] cmd,
input [M4_DATA_WID-1:0] word_in,
output reg [M4_DATA_WID-1:0] word_out,
input start_cmd,
output reg finish_cmd,
output in_loop,
output [DAC_DATA_WID-1:0] z_report
input assert_change,
output reg change_made,
input run_loop_in,
input [ADC_WID-1:0] setpt_in,
input [M4_CONSTS_WID-1:0] P_in,
input [M4_CONSTS_WID-1:0] I_in,
input [DELAY_WID-1:0] delay_in,
output [CYCLE_COUNT_WID-1:0] cycle_count,
output [DAC_DATA_WID-1:0] z_pos,
output [ADC_WID-1:0] z_measured
);
/************ ADC and DAC modules ***************/
@ -104,6 +108,7 @@ spi_master_ss #(
reg adc_arm;
reg adc_finished;
wire [ADC_WID-1:0] measured_value;
assign z_measured = measured_value;
wire adc_ready_to_arm_unused;
localparam [3-1:0] DAC_REGISTER = 3'b001;
@ -133,33 +138,30 @@ spi_master_ss_no_write #(
* Parameters can be adjusted on the fly by the user. The modifications
* cannot happen during a calculation, but calculations occur in a matter
* of milliseconds. Instead, modifications are checked and applied at the
* start of each iteration (CYCLE_START). Before this, the new values
* have to be buffered.
* start of each iteration (CYCLE_START).
*/
/* Setpoint: what should the ADC read */
reg signed [ADC_WID-1:0] setpt = 0;
reg signed [ADC_WID-1:0] setpt_buffer = 0;
/* Integral parameter */
reg signed [M4_CONSTS_WID-1:0] cl_I_reg = 0;
reg signed [M4_CONSTS_WID-1:0] cl_I_reg_buffer = 0;
/* Proportional parameter */
reg signed [M4_CONSTS_WID-1:0] cl_p_reg = 0;
reg signed [M4_CONSTS_WID-1:0] cl_p_reg_buffer = 0;
/* Delay parameter (to make the loop run slower) */
reg [DELAY_WID-1:0] dely = 0;
reg [DELAY_WID-1:0] dely_buffer = 0;
/************ Loop Control and Internal Parameters *************/
reg running = 0;
reg signed [DAC_DATA_WID-1:0] stored_dac_val = 0;
assign z_report = stored_dac_val;
assign z_pos = stored_dac_val;
reg [CYCLE_COUNT_WID-1:0] last_timer = 0;
assign cycle_count = last_timer;
reg [CYCLE_COUNT_WID-1:0] counting_timer = 0;
reg [M4_CONSTS_WID-1:0] adjval_prev = 0;
@ -214,12 +216,6 @@ control_loop_math #(
* ┃ ↓
* ┗━━━━━━━┛
****** Outline
* There are two systems: the read-write interface and the loop.
* The read-write interface allows another module (i.e. the CPU)
* to access and change constants. When a constant is changed the
* loop must reset the values that are preserved between loops
* (previous adjustment and previous delay).
*
* When the loop starts it must find the current value from the
* DAC and write to it. The value from the DAC is then adjusted
* with the output of the control loop. Afterwards it does not
@ -253,106 +249,21 @@ always @ (posedge clk) begin
end
end
/**** Read-Write control interface.
* `write_control` ensures that writes to the dirty bit do not happen when
* the main loop is clearing the dirty bit.
*/
wire write_control = state == WAIT_ON_DAC || !running;
reg dirty_bit = 0;
always @ (posedge clk) begin
if (!rst_L) begin
dirty_bit <= 0;
finish_cmd <= 0;
word_out <= 0;
end else if (start_cmd && !finish_cmd) begin
case (cmd)
M4_CONTROL_LOOP_NOOP:
finish_cmd <= 1;
M4_CONTROL_LOOP_NOOP | M4_CONTROL_LOOP_WRITE_BIT:
finish_cmd <= 1;
M4_CONTROL_LOOP_STATUS: begin
word_out[M4_DATA_WID-1:1] <= 0;
word_out[0] <= running;
finish_cmd <= 1;
end
M4_CONTROL_LOOP_STATUS | M4_CONTROL_LOOP_WRITE_BIT:
if (write_control) begin
running <= word_in[0];
finish_cmd <= 1;
dirty_bit <= 1;
end
M4_CONTROL_LOOP_SETPT: begin
word_out[M4_DATA_WID-1:ADC_WID] <= 0;
word_out[ADC_WID-1:0] <= setpt;
finish_cmd <= 1;
end
M4_CONTROL_LOOP_SETPT | M4_CONTROL_LOOP_WRITE_BIT:
if (write_control) begin
setpt_buffer <= word_in[ADC_WID-1:0];
finish_cmd <= 1;
dirty_bit <= 1;
end
M4_CONTROL_LOOP_P: begin
word_out <= cl_p_reg;
finish_cmd <= 1;
end
M4_CONTROL_LOOP_P | M4_CONTROL_LOOP_WRITE_BIT: begin
if (write_control) begin
cl_p_reg_buffer <= word_in;
finish_cmd <= 1;
dirty_bit <= 1;
end
end
M4_CONTROL_LOOP_I: begin
word_out <= cl_I_reg;
finish_cmd <= 1;
end
M4_CONTROL_LOOP_I | M4_CONTROL_LOOP_WRITE_BIT: begin
if (write_control) begin
cl_I_reg_buffer <= word_in;
finish_cmd <= 1;
dirty_bit <= 1;
end
end
M4_CONTROL_LOOP_DELAY: begin
word_out[M4_DATA_WID-1:DELAY_WID] <= 0;
word_out[DELAY_WID-1:0] <= dely;
finish_cmd <= 1;
end
M4_CONTROL_LOOP_DELAY | M4_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
M4_CONTROL_LOOP_ERR: begin
word_out[M4_DATA_WID-1:M4_E_WID] <= 0;
word_out[M4_E_WID-1:0] <= err_prev;
finish_cmd <= 1;
end
M4_CONTROL_LOOP_Z: begin
word_out[M4_DATA_WID-1:DAC_DATA_WID] <= 0;
word_out[DAC_DATA_WID-1:0] <= stored_dac_val;
finish_cmd <= 1;
end
M4_CONTROL_LOOP_CYCLES: begin
word_out[M4_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
end
assign in_loop = state != INIT_READ_FROM_DAC || running;
/* Reset the change acknowledge interface after the master
* stops its transfer.
*
* The module only writes to change_made in the main always block
* when state == CYCLE_START, so make sure that this does not
* compete with CYCLE_START.
*/
always @ (posedge clk) begin
if (!rst_L) begin
if (state != CYCLE_START && !assert_change && change_made)
change_made <= 0;
end
task reset_loop();
to_dac <= 0;
dac_arm <= 0;
state <= INIT_READ_FROM_DAC;
@ -366,12 +277,20 @@ always @ (posedge clk) begin
adc_arm <= 0;
arm_math <= 0;
endtask
always @ (posedge clk) begin
if (!rst_L) begin
reset_loop();
end else case (state)
INIT_READ_FROM_DAC: begin
if (running) begin
if (run_loop_in) begin
running <= 1;
to_dac <= {1'b1, DAC_REGISTER, 20'b0};
dac_arm <= 1;
state <= WAIT_FOR_DAC_READ;
end else begin
reset_loop();
end
end
WAIT_FOR_DAC_READ: begin
@ -396,21 +315,21 @@ always @ (posedge clk) begin
end
end
CYCLE_START: begin
if (!running) begin
state <= INIT_READ_FROM_DAC;
if (!run_loop_in) begin
reset_loop();
end else if (timer < dely) begin
timer <= timer + 1;
end else begin
/* On change of constants, previous values are invalidated. */
if (dirty_bit) begin
setpt <= setpt_buffer;
dely <= dely_buffer;
cl_I_reg <= cl_I_reg_buffer;
cl_p_reg <= cl_p_reg_buffer;
if (assert_change && !change_made) begin
change_made <= 1;
setpt <= setpt_in;
dely <= delay_in;
cl_I_reg <= I_in;
cl_p_reg <= P_in;
adjval_prev <= 0;
err_prev <= 0;
dirty_bit <= 0;
end
state <= WAIT_ON_ADC;

View File

@ -1,7 +0,0 @@
m4_changequote(`⟨', `⟩')m4_dnl
m4_changecom(⟨/*⟩, ⟨*/⟩)m4_dnl
m4_define(generate_macro, ⟨m4_dnl
#define $1 $2 m4_dnl
m4_define(M4_$1, $2)m4_dnl
⟩)m4_dnl
m4_include(control_loop_cmds.m4)

View File

@ -1,11 +0,0 @@
generate_macro(CONTROL_LOOP_NOOP, 0)
generate_macro(CONTROL_LOOP_STATUS, 1)
generate_macro(CONTROL_LOOP_SETPT, 2)
generate_macro(CONTROL_LOOP_P, 3)
generate_macro(CONTROL_LOOP_I, 4)
generate_macro(CONTROL_LOOP_ERR, 5)
generate_macro(CONTROL_LOOP_Z, 6)
generate_macro(CONTROL_LOOP_CYCLES, 7)
generate_macro(CONTROL_LOOP_DELAY, 8)
generate_macro(CONTROL_LOOP_CMD_WIDTH, 8)
generate_macro(CONTROL_LOOP_WRITE_BIT, (1 << (M4_CONTROL_LOOP_CMD_WIDTH-1)))

View File

@ -1,7 +0,0 @@
m4_changequote(`⟨', `⟩')m4_dnl
m4_changecom(⟨/*⟩, ⟨*/⟩)m4_dnl
m4_define(generate_macro, ⟨m4_dnl
`define $1 $2 m4_dnl
m4_define(M4_$1, $2)m4_dnl
⟩)m4_dnl
m4_include(control_loop_cmds.m4)

View File

@ -14,7 +14,6 @@
#include <verilated.h>
#include "control_loop_math_implementation.h"
#include "control_loop_cmds.h"
#include "Vcontrol_loop_sim_top.h"
using ModType = Vcontrol_loop_sim_top;
@ -41,32 +40,27 @@ static void init(int argc, char **argv) {
mod->rst_L = 1;
}
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) {
printf("sim top\n");
init(argc, argv);
Transfer func = Transfer{150, 0, 2, 1.1, 10, -1};
set_value(0b11010111000010100011110101110000101000111, CONTROL_LOOP_P);
/* Constant values must be sized to 64 bits, or else the compiler
* will think they are 32 bit and silently mess things up
*/
set_value((V)6 << CONSTS_FRAC, CONTROL_LOOP_I);
set_value(20, CONTROL_LOOP_DELAY);
set_value(10000, CONTROL_LOOP_SETPT);
set_value(1, CONTROL_LOOP_STATUS);
mod->P_in = 0b11010111000010100011110101110000101000111;
mod->I_in = (V)6 << CONSTS_FRAC;
mod->delay_in = 20;
mod->setpt_in = 10000;
mod->assert_change = 1;
mod->run_loop_in = 1;
mod->curset = 0;
for (int tick = 0; tick < 1000;) {
if (mod->change_made) {
mod->assert_change = 0;
}
run_clock();
if (mod->request && !mod->fulfilled) {
/* Verilator values are not sign-extended to the
@ -81,10 +75,6 @@ int main(int argc, char **argv) {
mod->fulfilled = 0;
tick++;
}
if (mod->finish_cmd) {
mod->start_cmd = 0;
}
}
mod->final();

View File

@ -3,7 +3,6 @@
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
`include "control_loop_cmds.vh"
module control_loop_sim_top #(
parameter ADC_WID = 18,
@ -16,6 +15,7 @@ module control_loop_sim_top #(
parameter DAC_DATA_WID = 20,
parameter DAC_WID = 24,
parameter DAC_WID_SIZ = 5,
parameter CYCLE_COUNT_WID = 18,
parameter CONSTS_WHOLE = 21,
parameter CONSTS_FRAC = 43,
@ -35,11 +35,18 @@ module control_loop_sim_top #(
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 assert_change,
output change_made,
input run_loop_in,
input [ADC_WID-1:0] setpt_in,
input [`CONSTS_WID-1:0] P_in,
input [`CONSTS_WID-1:0] I_in,
input [DELAY_WID-1:0] delay_in,
output [CYCLE_COUNT_WID-1:0] cycle_count,
output [DAC_DATA_WID-1:0] z_pos,
output [ADC_WID-1:0] z_measured
);
/* Emulate a control loop environment with simulator controlled
@ -101,6 +108,7 @@ control_loop #(
/* Keeping cycle half wait and conv wait the same
* since it doesn't matter for this simulation */
.CYCLE_COUNT_WID(CYCLE_COUNT_WID),
.CONSTS_WHOLE(CONSTS_WHOLE),
.CONSTS_FRAC(CONSTS_FRAC),
.CONSTS_SIZ(CONSTS_SIZ),
@ -124,11 +132,18 @@ control_loop #(
.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)
.assert_change(assert_change),
.change_made(change_made),
.run_loop_in(run_loop_in),
.setpt_in(setpt_in),
.P_in(P_in),
.I_in(I_in),
.delay_in(delay_in),
.cycle_count(cycle_count),
.z_pos(z_pos),
.z_measured(z_measured)
);
`ifdef VERILATOR

View File

@ -56,6 +56,8 @@ from litedram.modules import MT41K128M16
from litedram.frontend.dma import LiteDRAMDMAReader
from liteeth.phy.mii import LiteEthPHYMII
import mmio_descr
"""
Keep this diagram up to date! This is the wiring diagram from the ADC to
the named Verilog pins.
@ -128,28 +130,26 @@ class Base(Module, AutoCSR):
keyword arguments to pass all the arguments.
"""
def _make_csr(self, name, csrclass, csrlen, description, num=None):
""" Add a CSR for a pin `f"{name}_{num}"` with CSR type
`csrclass`. This will automatically handle the `i_` and
`o_` prefix in the keyword arguments.
def _make_csr(self, reg, num=None):
""" Add a CSR for a pin `f"{name}_{num}".`
This function is used to automate the creation of memory mapped
IO pins for all the converters on the device.
`csrclass` must be CSRStorage (Read-Write) or CSRStatus (Read only).
`csrlen` is the length in bits of the MMIO register. LiteX automatically
takes care of byte alignment, etc. so the length can be any positive
number.
Description is optional but recommended for debugging.
:param name: Name of the MMIO register without prefixes or numerical
suffix.
:param num: Numerical suffix of this MMIO register. This is the only
parameter that should change when adding multiple CSRs of the same
name.
"""
if name not in self.csrdict.keys():
self.csrdict[name] = csrlen
name = reg.name
if num is not None:
name = f"{name}_{num}"
csr = csrclass(csrlen, name=name, description=description)
if self.ro == "read-only":
csrclass = CSRStatus
else:
csrclass = CSRStorage
csr = csrclass(reg.blen, name=name, description=None)
setattr(self, name, csr)
if csrclass is CSRStorage:
@ -161,44 +161,13 @@ class Base(Module, AutoCSR):
def __init__(self, clk, sdram, platform):
self.kwargs = {}
self.csrdict = {}
for i in range(0,8):
self._make_csr("adc_sel", CSRStorage, 3, f"Select ADC {i} Output", num=i)
self._make_csr("dac_sel", CSRStorage, 3, f"Select DAC {i} Output", num=i)
self._make_csr("dac_finished", CSRStatus, 1, f"DAC {i} Transmission Finished Flag", num=i)
self._make_csr("dac_arm", CSRStorage, 1, f"DAC {i} Arm Flag", num=i)
self._make_csr("dac_recv_buf", CSRStatus, 24, f"DAC {i} Received Data", num=i)
self._make_csr("dac_send_buf", CSRStorage, 24, f"DAC {i} Data to Send", num=i)
# self._make_csr("wf_arm", CSRStorage, 1, f"Waveform {i} Arm Flag", num=i)
# self._make_csr("wf_halt_on_finish", CSRStorage, 1, f"Waveform {i} Halt on Finish Flag", num=i)
# self._make_csr("wf_finished", CSRStatus, 1, f"Waveform {i} Finished Flag", num=i)
# self._make_csr("wf_running", CSRStatus, 1, f"Waveform {i} Running Flag", num=i)
# self._make_csr("wf_time_to_wait", CSRStorage, 16, f"Waveform {i} Wait Time", num=i)
# self._make_csr("wf_refresh_start", CSRStorage, 1, f"Waveform {i} Data Refresh Start Flag", num=i)
# self._make_csr("wf_refresh_finished", CSRStatus, 1, f"Waveform {i} Data Refresh Finished Flag", num=i)
# self._make_csr("wf_start_addr", CSRStorage, 32, f"Waveform {i} Data Addr", num=i)
#
# port = sdram.crossbar.get_port()
# setattr(self, f"wf_sdram_{i}", LiteDRAMDMAReader(port))
# cur_sdram = getattr(self, f"wf_sdram_{i}")
#
# self.kwargs[f"o_wf_ram_dma_addr_{i}"] = cur_sdram.sink.address
# self.kwargs[f"i_wf_ram_word_{i}"] = cur_sdram.source.data
# self.kwargs[f"o_wf_ram_read_{i}"] = cur_sdram.sink.valid
# self.kwargs[f"i_wf_ram_valid_{i}"] = cur_sdram.source.valid
self._make_csr("adc_finished", CSRStatus, 1, f"ADC {i} Finished Flag", num=i)
self._make_csr("adc_arm", CSRStorage, 1, f"ADC {i} Arm Flag", num=i)
self._make_csr("adc_recv_buf", CSRStatus, 32, f"ADC {i} Received Data", num=i)
self._make_csr("cl_in_loop", CSRStatus, 1, "Control Loop Loop Enabled Flag")
self._make_csr("cl_cmd", CSRStorage, 8, "Control Loop Command Input")
self._make_csr("cl_word_in", CSRStorage, 64, "Control Loop Data Input")
self._make_csr("cl_word_out", CSRStatus, 64, "Control Loop Data Output")
self._make_csr("cl_start_cmd", CSRStorage, 1, "Control Loop Command Start Flag")
self._make_csr("cl_finish_cmd", CSRStatus, 1, "Control Loop Command Finished Flag")
self._make_csr("cl_z_report", CSRStatus, 1, "Control Loop Z Setting")
for reg in mmio_descr.registers:
if reg.num > 1:
for i in range(0,reg.num):
self._make_csr(reg,i)
else:
self._make_csr(reg)
self.kwargs["i_clk"] = clk
self.kwargs["i_rst_L"] = ~platform.request("module_reset")
@ -209,14 +178,8 @@ class Base(Module, AutoCSR):
self.kwargs["o_adc_conv"] = platform.request("adc_conv")
self.kwargs["i_adc_sdo"] = platform.request("adc_sdo")
self.kwargs["o_adc_sck"] = platform.request("adc_sck")
# self.kwargs["o_test_clock"] = platform.request("test_clock")
self.kwargs["o_set_low"] = platform.request("differntial_output_low")
""" Dump all MMIO pins to a JSON file with their exact bit widths. """
with open("csr_bitwidth.json", mode='w') as f:
import json
json.dump(self.csrdict, f)
self.specials += Instance("base", **self.kwargs)
# Clock and Reset Generator

View File

@ -11,7 +11,6 @@ platform-runcmd = echo LiteX/VexRiscv
PLATFORM_RISCV_XLEN = 32
PLATFORM_RISCV_ABI = ilp32
#PLATFORM_RISCV_ISA = rv32ima ## XXX: Broken on new binutils
PLATFORM_RISCV_ISA = rv32ima_zicsr_zifencei
PLATFORM_RISCV_CODE_MODEL = medany
platform-objs-y += platform.o