picorv32 integration, take 1

This commit is contained in:
Peter McGoron 2024-02-02 15:24:18 -05:00
parent 9db87cb8ee
commit fbd3dcef2e
17 changed files with 3551 additions and 807 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +0,0 @@
# Copyright 2023 (C) Peter McGoron
# This file is a part of Upsilon, a free and open source software project.
# For license terms, refer to the files in `doc/copying` in the Upsilon
# source distribution.
# Makefile for tests and hardware verification.
.PHONY: test clean codegen
include ../common.makefile
all: test codegen
test: obj_dir/Vspi_switch
CODEGEN_FILES= spi_master_ss_preprocessed.v spi_master_preprocessed.v \
spi_master_no_write_preprocessed.v \
spi_master_no_read_preprocessed.v \
spi_master_ss_no_read_preprocessed.v \
spi_master_ss_no_write_preprocessed.v spi_switch_preprocessed.v
codegen: ${CODEGEN_FILES}
SRC= spi_switch.v spi_switch_sim.cpp
obj_dir/Vspi_switch.mk: $(SRC)
verilator --cc --exe -Wall \
$(SRC)
obj_dir/Vspi_switch: obj_dir/Vspi_switch.mk $(SRC)
cd obj_dir && make -f Vspi_switch.mk
./obj_dir/Vspi_switch
clean:
rm -rf obj_dir/ ${CODEGEN_FILES}

View File

@ -1,148 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
/* Dynamically adjustable DAC ramping.
* Given an increment voltage and a speed setting, increase the voltage
* to that voltage in increments over a period of time.
* This might not be neccessary for now but I wrote it for possible future
* use.
*/
module ramp #(
parameter DAC_DATA_WID = 20,
parameter DAC_WID = 24,
parameter WAIT_WID = 8,
parameter [WAIT_WID-1:0] READ_WAIT = 10
) (
input clk,
input arm,
input read_setting,
output reg finished,
output reg ready,
input [DAC_WID-1:0] mosi,
output [DAC_WID-1:0] miso,
output reg arm_transfer,
input finished_transfer
input signed [DAC_DATA_WID-1:0] move_to,
input signed [DAC_DATA_WID-1:0] stepsiz,
input signed [DAC_DATA_WID-1:0] wait_time,
output reg signed [DAC_DATA_WID-1:0] setting
);
localparam WAIT_ON_ARM = 0;
localparam WAIT_ON_READ_PART_1 = 1;
localparam WAIT_ON_READ_PART_2 = 2;
localparam WAIT_ON_WRITE = 3;
localparam WAIT_ON_TRANSFER = 4;
localparam WAIT_ON_DISARM = 5;
localparam WAIT_ON_READ_DISARM = 6;
reg [3-1:0] state = WAIT_ON_ARM;
reg [WAIT_WID-1:0] timer = 0;
localparam SIGN_POS = 0;
localparam SIGN_NEG = 1;
reg step_sign = 0;
reg last_transfer = 0;
`define DAC_CMD_WID (DAC_WID - DAC_DATA_WID)
localparam [`DAC_CMD_WID-1:0] read_reg = 4'b1001;
localparam [`DAC_CMD_WID-1:0] write_reg = 4'b0001;
task start_transfer();
case (step_sign)
SIGN_POS: if (setting + stepsiz >= move_to) begin
mosi[DAC_DATA_WID-1:0] <= setting;
last_transfer <= 1;
end else begin
mosi[DAC_DATA_WID-1:0] <= setting + stepsiz;
end
SIGN_NEG: if (setting - stepsiz <= move_to) begin
mosi[DAC_DATA_WID-1:0] <= setting;
last_transfer <= 1;
end else begin
mosi[DAC_DATA_WID-1:0] <= setting - stepsiz;
end
endcase
arm_transfer <= 1;
endtask
always @ (posedge clk) begin
case (state)
WAIT_ON_ARM: if (read_setting) begin
mosi <= {read_reg, {(DAC_DATA_WID){1'b0}}};
arm_transfer <= 1;
state <= WAIT_ON_READ;
ready <= 0;
end else if (arm) begin
ready <= 0;
last_transfer <= 0;
if (realstep != 0) begin
state <= WAIT_ON_WRITE;
end
mosi[DAC_WID-1:DAC_DATA_WID] <= write_reg;
/* 0 for positve, 1 for negative */
step_sign <= move_to > setting;
timer <= wait_time;
end else begin
ready <= 1;
end
/* Why put the wait here? If ramping is necessary then any abrupt
* changes in voltages can be disastrous. After each write the
* design always waits, even if ramping is stopped or done, so
* the next ramp always executes when a safe time has elapsed.
*/
WAIT_ON_WRITE: if (timer < wait_time) begin
timer <= timer + 1;
end else if (!arm || last_transfer) begin
state <= WAIT_ON_DISARM;
finished <= 1;
end else if (arm) begin
start_transfer();
state <= WAIT_ON_TRANSFER;
end
WAIT_ON_TRANSFER: if (finished_transfer) begin
setting <= mosi[DAC_DATA_WID-1:0];
timer <= 0;
state <= WAIT_ON_WRITE;
finished <= 1;
end
WAIT_ON_DISARM: if (!arm) begin
state <= WAIT_ON_ARM;
finished <= 0;
end
WAIT_ON_READ_PART_1: if (finished_transfer) begin
state <= WAIT_ON_READ_PART_2;
arm_transfer <= 0;
mosi <= 0;
timer <= 0;
end
WAIT_ON_READ_PART_2: if (timer < read_wait) begin
read_wait <= read_wait + 1;
end else if (!arm_transfer) begin
arm_transfer <= 1;
end else if (finished_transfer) begin
state <= WAIT_ON_READ_DISARM;
finished <= 1;
arm_transfer <= 0;
setting <= miso[DAC_DATA_WID-1:0];
end
WAIT_ON_READ_DISARM: if (!read_setting) begin
state <= WAIT_ON_ARM;
finished <= 0;
end
endcase
end
endmodule

View File

@ -1,12 +1,7 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
/* (c) Peter McGoron 2022 v0.3
* 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/.
/* (c) Peter McGoron 2022-2024 v0.4
*
* This code is disjunctively dual-licensed under the MPL v2.0, or the
* CERN-OHL-W v2.
*/
/* CYCLE_HALF_WAIT should take into account the setup time of the slave
@ -14,17 +9,9 @@
* the input).
*/
module
`ifdef SPI_MASTER_NO_READ
spi_master_no_read
`else
`ifdef SPI_MASTER_NO_WRITE
spi_master_no_write
`else
spi_master
`endif
`endif
#(
module spi_master #(
parameter ENABLE_MISO = 1, // Enable MISO and from_slave port
parameter ENABLE_MOSI = 1, // Enable MOSI and to_slave port
parameter WID = 24, // Width of bits per transaction.
parameter WID_LEN = 5, // Length in bits required to store WID
parameter CYCLE_HALF_WAIT = 1, // Half of the wait time of a cycle minus 1.
@ -32,25 +19,22 @@ spi_master
parameter TIMER_LEN = 3, // Length in bits required to store CYCLE_HALF_WAIT
parameter POLARITY = 0, // 0 = sck idle low, 1 = sck idle high
parameter PHASE = 0 // 0 = rising-read falling-write, 1 = rising-write falling-read.
)
(
) (
input clk,
input rst_L,
`ifndef SPI_MASTER_NO_READ
output reg [WID-1:0] from_slave,
input miso,
`endif
`ifndef SPI_MASTER_NO_WRITE
input [WID-1:0] to_slave,
output reg mosi,
`endif
output reg sck_wire,
output reg finished,
output reg ready_to_arm,
input arm
);
`ifndef SPI_MASTER_NO_READ
/* MISO is almost always an external wire, so buffer it.
* This might not be necessary, since the master and slave do not respond
* immediately to changes in the wires, but this is just to be safe.
@ -61,11 +45,10 @@ spi_master
reg miso_hot = 0;
reg read_miso = 0;
always @ (posedge clk) begin
always @ (posedge clk) if (ENABLE_MISO == 1) begin
read_miso <= miso_hot;
miso_hot <= miso;
end
`endif
parameter WAIT_ON_ARM = 0;
parameter ON_CYCLE = 1;
@ -76,9 +59,7 @@ reg [1:0] state = WAIT_ON_ARM;
reg [WID_LEN-1:0] bit_counter = 0;
reg [TIMER_LEN-1:0] timer = 0;
`ifndef SPI_MASTER_NO_WRITE
reg [WID-1:0] send_buf = 0;
`endif
reg sck = 0;
assign sck_wire = sck;
@ -89,25 +70,23 @@ task idle_state();
end else begin
sck <= 1;
end
`ifndef SPI_MASTER_NO_WRITE
mosi <= 0;
`endif
if (ENABLE_MOSI == 1) mosi <= 0;
timer <= 0;
bit_counter <= 0;
endtask
task read_data();
`ifndef SPI_MASTER_NO_READ
from_slave <= from_slave << 1;
from_slave[0] <= read_miso;
`endif
if (ENABLE_MISO == 1) begin
from_slave <= from_slave << 1;
from_slave[0] <= read_miso;
end
endtask
task write_data();
`ifndef SPI_MASTER_NO_WRITE
mosi <= send_buf[WID-1];
send_buf <= send_buf << 1;
`endif
if (ENABLE_MOSI == 1) begin
mosi <= send_buf[WID-1];
send_buf <= send_buf << 1;
end
endtask
task setup_bits();
@ -118,15 +97,15 @@ task setup_bits();
* For mode 01 and mode 10, the first action is a WRITE.
*/
if (POLARITY == PHASE) begin
`ifndef SPI_MASTER_NO_WRITE
mosi <= to_slave[WID-1];
send_buf <= to_slave << 1;
`endif
if (ENABLE_MOSI == 1) begin
mosi <= to_slave[WID-1];
send_buf <= to_slave << 1;
end
state <= CYCLE_WAIT;
end else begin
`ifndef SPI_MASTER_NO_WRITE
send_buf <= to_slave;
`endif
if (ENABLE_MISO == 1) begin
send_buf <= to_slave;
end
state <= ON_CYCLE;
end
endtask
@ -149,12 +128,8 @@ always @ (posedge clk) begin
finished <= 0;
state <= WAIT_ON_ARM;
ready_to_arm <= 1;
`ifndef SPI_MASTER_NO_READ
from_slave <= 0;
`endif
`ifndef SPI_MASTER_NO_WRITE
send_buf <= 0;
`endif
if (ENABLE_MISO == 1) from_slave <= 0;
if (ENABLE_MOSI == 1) send_buf <= 0;
end else case (state)
WAIT_ON_ARM: begin
`ifdef SIMULATION
@ -230,4 +205,3 @@ always @ (posedge clk) begin
end
endmodule
`undefineall

View File

@ -1,8 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
`define SPI_MASTER_NO_READ
/* verilator lint_off DECLFILENAME */
`include "spi_master.v"

View File

@ -1,8 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
`define SPI_MASTER_NO_WRITE
/* verilator lint_off DECLFILENAME */
`include "spi_master.v"

View File

@ -1,9 +1,126 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
/* (c) Peter McGoron 2022 v0.4
*
* This code is disjunctively dual-licensed under the MPL v2.0, or the
* CERN-OHL-W v2.
*/
`define SPI_MASTER_SS_NAME spi_master_ss
`define SPI_MASTER_NAME spi_master
/* verilator lint_off DECLFILENAME */
`include "spi_master_ss_template.v"
/* spi master with integrated ability to wait a certain amount of cycles
* after activating SS.
*/
module spi_master_ss
#(
parameter SS_WAIT = 1, /* Amount of cycles to wait for SS
to enable */
parameter SS_WAIT_TIMER_LEN = 2, /* Amount of bits required to
store the SS wait time */
parameter ENABLE_MISO = 1,
parameter ENABLE_MOSI = 1,
parameter WID = 24,
parameter WID_LEN = 5,
parameter CYCLE_HALF_WAIT = 1,
parameter TIMER_LEN = 3,
parameter POLARITY = 0,
parameter PHASE = 0
) (
input clk,
input rst_L,
output [WID-1:0] from_slave,
input miso,
input [WID-1:0] to_slave,
output mosi,
output sck_wire,
output finished,
output ready_to_arm,
output ss_L,
input arm
);
reg ss = 0;
reg arm_master = 0;
assign ss_L = !ss;
spi_master #(
.ENABLE_MISO(ENABLE_MISO),
.ENABLE_MOSI(ENABLE_MOSI),
.WID(WID),
.WID_LEN(WID_LEN),
.CYCLE_HALF_WAIT(CYCLE_HALF_WAIT),
.TIMER_LEN(TIMER_LEN),
.POLARITY(POLARITY),
.PHASE(PHASE)
) master (
.clk(clk),
.rst_L(rst_L),
.from_slave(from_slave),
.miso(miso),
.to_slave(to_slave),
.mosi(mosi),
.sck_wire(sck_wire),
.finished(finished),
.ready_to_arm(ready_to_arm),
.arm(arm_master)
);
localparam WAIT_ON_ARM = 0;
localparam WAIT_ON_SS = 1;
localparam WAIT_ON_MASTER = 2;
localparam WAIT_ON_ARM_DEASSERT = 3;
reg [2:0] state = WAIT_ON_ARM;
reg [SS_WAIT_TIMER_LEN-1:0] timer = 0;
task master_arm();
arm_master <= 1;
state <= WAIT_ON_MASTER;
endtask
always @ (posedge clk) begin
if (!rst_L) begin
state <= WAIT_ON_ARM;
timer <= 0;
arm_master <= 0;
ss <= 0;
end else case (state)
WAIT_ON_ARM: begin
if (arm) begin
timer <= 1;
if (SS_WAIT == 0) begin
master_arm();
end else begin
timer <= 1;
state <= WAIT_ON_SS;
end
ss <= 1;
end
end
WAIT_ON_SS: begin
if (timer == SS_WAIT) begin
master_arm();
end else begin
timer <= timer + 1;
end
end
WAIT_ON_MASTER: begin
if (finished) begin
state <= WAIT_ON_ARM_DEASSERT;
ss <= 0;
end
end
WAIT_ON_ARM_DEASSERT: begin
if (!arm) begin
state <= WAIT_ON_ARM;
arm_master <= 0;
end
end
endcase
end
endmodule

View File

@ -1,10 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
`define SPI_MASTER_SS_NAME spi_master_ss_no_read
`define SPI_MASTER_NAME spi_master_no_read
`define SPI_MASTER_NO_READ
/* verilator lint_off DECLFILENAME */
`include "spi_master_ss_template.v"

View File

@ -1,10 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
`define SPI_MASTER_SS_NAME spi_master_ss_no_write
`define SPI_MASTER_NAME spi_master_no_write
`define SPI_MASTER_NO_WRITE
/* verilator lint_off DECLFILENAME */
`include "spi_master_ss_template.v"

View File

@ -1,128 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
/* (c) Peter McGoron 2022 v0.3
* 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/.
*/
/* spi master with integrated ability to wait a certain amount of cycles
* after activating SS.
*/
module `SPI_MASTER_SS_NAME
#(
parameter WID = 24,
parameter WID_LEN = 5,
parameter CYCLE_HALF_WAIT = 1,
parameter TIMER_LEN = 3,
parameter SS_WAIT = 1,
parameter SS_WAIT_TIMER_LEN = 2,
parameter POLARITY = 0,
parameter PHASE = 0
)
(
input clk,
input rst_L,
`ifndef SPI_MASTER_NO_READ
output [WID-1:0] from_slave,
input miso,
`endif
`ifndef SPI_MASTER_NO_WRITE
input [WID-1:0] to_slave,
output reg mosi,
`endif
output sck_wire,
output finished,
output ready_to_arm,
output ss_L,
input arm
);
reg ss = 0;
reg arm_master = 0;
assign ss_L = !ss;
`SPI_MASTER_NAME #(
.WID(WID),
.WID_LEN(WID_LEN),
.CYCLE_HALF_WAIT(CYCLE_HALF_WAIT),
.TIMER_LEN(TIMER_LEN),
.POLARITY(POLARITY),
.PHASE(PHASE)
) master (
.clk(clk),
.rst_L(rst_L),
`ifndef SPI_MASTER_NO_READ
.from_slave(from_slave),
.miso(miso),
`endif
`ifndef SPI_MASTER_NO_WRITE
.to_slave(to_slave),
.mosi(mosi),
`endif
.sck_wire(sck_wire),
.finished(finished),
.ready_to_arm(ready_to_arm),
.arm(arm_master)
);
localparam WAIT_ON_ARM = 0;
localparam WAIT_ON_SS = 1;
localparam WAIT_ON_MASTER = 2;
localparam WAIT_ON_ARM_DEASSERT = 3;
reg [2:0] state = WAIT_ON_ARM;
reg [SS_WAIT_TIMER_LEN-1:0] timer = 0;
task master_arm();
arm_master <= 1;
state <= WAIT_ON_MASTER;
endtask
always @ (posedge clk) begin
if (!rst_L) begin
state <= WAIT_ON_ARM;
timer <= 0;
arm_master <= 0;
ss <= 0;
end else case (state)
WAIT_ON_ARM: begin
if (arm) begin
timer <= 1;
if (SS_WAIT == 0) begin
master_arm();
end else begin
timer <= 1;
state <= WAIT_ON_SS;
end
ss <= 1;
end
end
WAIT_ON_SS: begin
if (timer == SS_WAIT) begin
master_arm();
end else begin
timer <= timer + 1;
end
end
WAIT_ON_MASTER: begin
if (finished) begin
state <= WAIT_ON_ARM_DEASSERT;
ss <= 0;
end
end
WAIT_ON_ARM_DEASSERT: begin
if (!arm) begin
state <= WAIT_ON_ARM;
arm_master <= 0;
end
end
endcase
end
endmodule
`undefineall

View File

@ -0,0 +1,106 @@
/* (c) Peter McGoron 2022 v0.4
*
* This code is disjunctively dual-licensed under the MPL v2.0, or the
* CERN-OHL-W v2.
*/
module spi_master_ss_wb
#(
parameter BUS_WID = 32, /* Width of a request on the bus. */
parameter SS_WAIT = 1,
parameter SS_WAIT_TIMER_LEN = 2,
parameter ENABLE_MISO = 1,
parameter ENABLE_MOSI = 1,
parameter WID = 24,
parameter WID_LEN = 5,
parameter CYCLE_HALF_WAIT = 1,
parameter TIMER_LEN = 3,
parameter POLARITY = 0,
parameter PHASE = 0
) (
input clk,
input rst_L,
input miso,
output mosi,
output sck_wire,
output ss_L
input wb_cyc,
input wb_stb,
input wb_we,
input [(BUS_WID)/4-1:0] wb_sel,
input [BUS_WID-1:0] wb_addr,
input [BUS_WID-1:0] wb_dat_w,
output reg wb_ack,
output reg [BUS_WID-1:0] wb_dat_r,
);
/* Address map:
All words are little endian. Access must be word-aligned and word-level
or undefined behavior will occur.
word 0: ready_to_arm | (finished << 1) (RW)
word 1: arm (RO)
word 2: from_slave (RO)
word 3: to_slave (RW)
*/
wire [WID-1:0] from_slave;
reg [WID-1:0] to_slave;
wire finished;
wire ready_to_arm;
reg arm;
spi_master_ss #(
.SS_WAIT(SS_WAIT),
.SS_WAIT_TIMER_LEN(SS_WAIT_TIMER_LEN),
.ENABLE_MISO(ENABLE_MISO),
.ENABLE_MOSI(ENABLE_MOSI),
.WID(WID),
.WID_LEN(WID_LEN),
.CYCLE_HALF_WAIT(CYCLE_HALF_WAIT),
.TIMER_LEN(TIMER_LEN),
.POLARITY(POLARITY),
.PHASE(PHASE)
) spi (
.clk(clk),
.rst_L(rst_L),
.from_slave(from_slave),
.miso(miso),
.to_slave(to_slave),
.mosi(mosi),
.sck_wire(sck_wire),
.finished(finished),
.ready_to_arm(ready_to_arm),
.ss_L(ss_L),
.arm(arm)
);
always @ (posedge clk) if (wb_cyc && wb_stb && !wb_ack) begin
if (!wb_we) case (wb_addr[4-1:0])
4'h0: wb_dat_r <= {30'b0, finished, ready_to_arm};
4'h4: wb_dat_r <= {31'b0, arm};
4'h8: wb_dat_r <= from_slave;
4'hC: wb_dat_r <= to_slave;
default: wb_dat_r <= 0;
endcase else case (wb_addr[4-1:0])
4'h4: arm <= wb_dat_w[0];
4'hC: to_slave <= wb_dat_w;
default: ;
end
wb_ack <= 1;
end else begin
wb_ack <= 0;
end
endmodule

View File

@ -1,171 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
/* (c) Peter McGoron 2022 v0.3
* 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 rst_L,
input sck,
input ss_L,
`ifndef SPI_SLAVE_NO_READ
output reg [WID-1:0] from_master,
input 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
);
`ifndef SPI_SLAVE_NO_READ
/* MOSI is almost always an external wire, so buffer it. */
reg mosi_hot = 0;
reg read_mosi = 0;
always @ (posedge clk) begin
read_mosi <= mosi_hot;
mosi_hot <= mosi;
end
`endif
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] <= read_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[WID_LEN-1:0]) begin
err <= ready_at_start;
end else begin
bit_counter <= bit_counter + 1;
end
endtask
always @ (posedge clk) begin
if (!rst_L) begin
sck_delay <= 0;
bit_counter <= 0;
ss_delay <= 0;
ready_at_start <= 0;
`ifndef SPI_SLAVE_NO_READ
from_master <= 0;
`endif
`ifndef SPI_SLAVE_NO_WRITE
miso <= 0;
send_buf <= 0;
`endif
finished <= 0;
err <= 0;
end else 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
end
endmodule
`undefineall

View File

@ -1,8 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
`define SPI_SLAVE_NO_READ
/* verilator lint_off DECLFILENAME */
`include "spi_slave.v"

View File

@ -1,8 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
`define SPI_SLAVE_NO_WRITE
/* verilator lint_off DECLFILENAME */
`include "spi_slave.v"

View File

@ -1,60 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
/* This module is a co-operative crossbar for the wires only. Each end
* implements its own SPI master.
*
* This crossbar is entirely controlled by the kernel.
*/
module spi_switch #(
parameter PORTS = 3
) (
/* verilator lint_off UNUSEDSIGNAL */
input [PORTS-1:0] select,
/* verilator lint_on UNUSEDSIGNAL */
output reg mosi,
input miso,
output reg sck,
output reg ss_L,
input [PORTS-1:0] mosi_ports,
output reg [PORTS-1:0] miso_ports,
input [PORTS-1:0] sck_ports,
input [PORTS-1:0] ss_L_ports
);
/* Avoid using for loops, they might not synthesize correctly.
* Do things the old, dumb way instead.
*
* TODO: Instead of bit vector, use regular numbers
*/
`define do_select(n) \
mosi = mosi_ports[n]; \
miso_ports = {{(PORTS-1){1'b0}},miso} << n; \
sck = sck_ports[n]; \
ss_L = ss_L_ports[n]
`define check_select(n) \
if (select[n]) begin \
`do_select(n); \
end
generate if (PORTS == 3) always @(*) begin
`check_select(2)
else `check_select(1)
else begin
`do_select(0);
end
end else always @(*) begin
`check_select(1)
else begin
`do_select(0);
end
end endgenerate
endmodule
`undefineall

View File

@ -1,56 +0,0 @@
/* Copyright 2023 (C) Peter McGoron
* This file is a part of Upsilon, a free and open source software project.
* For license terms, refer to the files in `doc/copying` in the Upsilon
* source distribution.
*/
#include "../util.hpp"
#include "Vspi_switch.h"
Vspi_switch *tb;
static void set_and_check(unsigned int selected, unsigned int num, unsigned int expected) {
tb->miso = 1;
tb->mosi_ports = 1 << num;
tb->sck_ports = 1 << num;
tb->ss_L_ports = 1 << num;
tb->eval();
my_assert(tb->mosi == expected, "%u != %u", tb->mosi, expected);
my_assert(tb->sck == expected, "%u != %u", tb->sck, expected);
my_assert(tb->ss_L == expected, "%u != %u", tb->ss_L, expected);
my_assert(tb->miso_ports == 1 << selected, "%u != %u", tb->miso_ports, 1 << selected);
}
int main(int argc, char **argv) {
Verilated::commandArgs(argc, argv);
Verilated::traceEverOn(true);
tb = new Vspi_switch();
printf("Default behavior.\n");
tb->select = 0;
set_and_check(0, 0, 1);
set_and_check(0, 1, 0);
set_and_check(0, 2, 0);
printf("Selecting the first port.\n");
tb->select = 1;
set_and_check(0, 0, 1);
set_and_check(0, 1, 0);
set_and_check(0, 2, 0);
printf("Selecting the second port.\n");
tb->select = 1 << 1;
set_and_check(1, 0, 0);
set_and_check(1, 1, 1);
set_and_check(1, 2, 0);
printf("Selecting the third port.\n");
tb->select = 1 << 2;
set_and_check(2, 0, 0);
set_and_check(2, 1, 0);
set_and_check(2, 2, 1);
tb->final();
delete tb;
return 0;
}

View File

@ -33,7 +33,7 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
##########################################################################
# Copyright 2023 (C) Peter McGoron
# Copyright 2023-2024 (C) Peter McGoron
#
# This file is a part of Upsilon, a free and open source software project.
# For license terms, refer to the files in `doc/copying` in the Upsilon
@ -58,7 +58,11 @@ from litedram.modules import MT41K128M16
from litedram.frontend.dma import LiteDRAMDMAReader
from liteeth.phy.mii import LiteEthPHYMII
import mmio_descr
from math import log2, floor
def minbits(n):
""" Return the amount of bits necessary to store n. """
return floor(log2(n) + 1)
"""
Keep this diagram up to date! This is the wiring diagram from the ADC to
@ -101,98 +105,192 @@ io = [
("test_clock", 0, Pins("P18"), IOStandard("LVCMOS33"))
]
# TODO: Assign widths to ADCs here using parameters
class PreemptiveInterface(Module, AutoCSR):
""" A preemptive interface is a manually controlled Wishbone interface
that stands between multiple masters (potentially interconnects) and a
single slave. A master controls which master (or interconnect) has access
to the slave. This is to avoid bus contention by having multiple buses. """
class Base(Module, AutoCSR):
""" The subclass AutoCSR will automatically make CSRs related
to this class when those CSRs are attributes (i.e. accessed by
`self.csr_name`) of instances of this class. (CSRs are MMIO,
they are NOT RISC-V CSRs!)
Since there are a lot of input and output wires, the CSRs are
assigned using `setattr()`.
CSRs are for input wires (`CSRStorage`) or output wires
(`CSRStatus`). The first argument to the CSR constructor is
the amount of bits the CSR takes. The `name` keyword argument
is required since the constructor needs the name of the attribute.
The `description` keyword is used for documentation.
In LiteX, modules in separate Verilog files are instantiated as
self.specials += Instance(
"module_name",
PARAMETER_NAME=value,
i_input = input_port,
o_output = output_port,
...
)
Since the "base" module has a bunch of repeated input and output
pins that have to be connected to CSRs, the LiteX wrapper uses
keyword arguments to pass all the arguments.
"""
def _make_csr(self, reg, num=None):
""" Add a CSR for a pin `f"{name}_{num}".`
: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.
def __init__(self, masters_len, slave):
"""
:param masters_len: The amount of buses accessing this slave. This number
must be greater than one.
:param slave: The slave device. This object must have an Interface object
accessable as ``bus``.
"""
name = reg.name
if num is not None:
name = f"{name}_{num}"
assert masters_len > 1
self.buses = []
self.master_select = CSRStorage(masters_len, name='master_select', description='RW bitstring of which master interconnect to connect to')
self.slave = slave
if reg.rwperm == "read-only":
csrclass = CSRStatus
else:
csrclass = CSRStorage
for i in range(masters_len):
# Add the slave interface each master interconnect sees.
self.buses.append(Interface(data_width=32, address_width=32, addressing="byte")
self.comb += [
self.buses[i].cti.eq(0),
self.buses[i].bte.eq(0),
]
csr = csrclass(reg.blen, name=name, description=None)
setattr(self, name, csr)
"""
Construct a combinatorial case statement. In verilog, the if
statment would look like
if csrclass is CSRStorage:
self.kwargs[f'i_{name}'] = csr.storage
elif csrclass is CSRStatus:
self.kwargs[f'o_{name}'] = csr.status
else:
raise Exception(f"Unknown class {csrclass}")
always @ (*) case (master_select)
1: begin
// Bus assignments...
end
2: begin
// Bus assignments...
end
// more cases:
default:
// assign everything to master 0
end
def __init__(self, clk, sdram, platform):
self.kwargs = {}
The If statement in Migen (Python HDL) is an object with a method
called "ElseIf" and "Else", that return objects with the specified
case attached. Instead of directly writing an If statement into
the combinatorial block, the If statement is constructed in a
for loop.
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)
The "assign_for_case" function constructs the body of the If
statement. It assigns all output ports to avoid latches.
"""
self.kwargs["i_clk"] = clk
self.kwargs["i_rst_L"] = ~platform.request("module_reset")
self.kwargs["i_dac_miso"] = platform.request("dac_miso")
self.kwargs["o_dac_mosi"] = platform.request("dac_mosi")
self.kwargs["o_dac_sck"] = platform.request("dac_sck")
self.kwargs["o_dac_ss_L"] = platform.request("dac_ss_L")
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_set_low"] = platform.request("differntial_output_low")
def assign_for_case(i):
asn = [
self.slave.bus.cyc.eq(self.buses[i].cyc),
self.slave.bus.stb.eq(self.buses[i].stb),
self.slave.bus.we.eq(self.buses[i].we),
self.slave.bus.sel.eq(self.buses[i].sel),
self.slave.bus.addr.eq(self.buses[i].addr),
self.slave.bus.dat_w.eq(self.buses[i].dat_w),
self.slave.bus.ack.eq(self.buses[i].ack),
self.slave.bus.dat_r.eq(self.buses[i].dat_r),
]
self.specials += Instance("base", **self.kwargs)
for j in range(masters_len):
if j == i:
continue
asn += [
self.buses[i].ack.eq(0),
self.buses[i].ack.eq(0),
]
return asn
cases = {"default": assign_for_case(0)}
for i in range(1, masters_len):
cases[i] = assign_for_case(i)
self.comb += Case(self.master_select, cases)
class SPIMaster(Module):
def __init__(self, rst, miso, mosi, sck, ss,
polarity = 0,
phase = 0,
ss_wait = 1,
enable_miso = 1,
enable_mosi = 1,
spi_wid = 24,
spi_cycle_half_wait = 1,
):
self.bus = Interface(data_width = 32, address_width=32, addressing="word")
self.comb += [
self.bus.cti.eq(0),
self.bus.bte.eq(0),
]
self.specials += Instance("spi_master_ss_wb",
SS_WAIT = ss_wait,
SS_WAIT_TIMER_LEN = minwid(ss_wait),
CYCLE_HALF_WAIT = spi_cycle_half_wait,
TIMER_LEN = minwid(spi_cycle_half_wait),
WID = spi_wid,
WID_LEN = minwid(spi_wid),
ENABLE_MISO = enable_miso,
ENABLE_MOSI = enable_mosi,
POLARITY = polarity,
PHASE = phase,
i_clk = ClockSignal(),
i_rst_L = rst,
i_miso = miso,
o_mosi = mosi,
o_sck_wire = sck,
o_ss_L = ss,
i_wb_cyc = self.bus.cyc,
i_wb_stb = self.bus.stb,
i_wb_we = self.bus.we,
i_wb_sel = self.bus.sel,
i_wb_addr = self.bus.adr,
i_wb_dat_w = self.bus.dat_w,
o_wb_ack = self.bus.ack,
o_wb_dat_r = self.bus.dat_r,
)
# TODO: Generalize CSR stuff
class ControlLoopParameters(Module, AutoCSR):
def __init__(self):
self.cl_I = CSRStorage(32, description='Integral parameter')
self.cl_P = CSRStorage(32, description='Proportional parameter')
self.deltaT = CSRStorage(32, description='Wait parameter')
self.setpt = CSRStorage(32, description='Setpoint')
self.zset = CSRStatus(32, description='Set Z position')
self.zpos = CSRStatus(32, description='Measured Z position')
self.bus = Interface(data_width = 32, address_width = 32, addressing="word")
self.region = SoCRegion(size=minbits(0x14), cached=False)
self.comb += [
self.bus.cti.eq(0),
self.bus.bte.eq(0),
]
self.sync += [
If(self.bus.cyc && self.bus.stb && !self.bus.ack,
Case(self.bus.adr[0:4], {
0x0: self.bus.dat_r.eq(self.cl_I),
0x4: self.bus.dat_r.eq(self.cl_P),
0x8: self.bus.dat_r.eq(self.deltaT),
0xC: self.bus.dat_r.eq(self.setpt),
0x10: If(self.bus.we,
self.bus.zset.eq(self.bus.dat_w)
).Else(
self.bus.dat_r.eq(self.bus.zset)
),
0x14: If(self.bus.we,
self.bus.zpos.eq(self.bus.dat_w),
).Else(
self.bus.dat_r.eq(self.bus.zpos)
),
}),
self.bus.ack.eq(1),
).Else(
self.bus.ack.eq(0),
)
]
class BRAM(Module):
def __init__(self, clk):
""" A BRAM (block ram) is a memory store that is completely separate from
the system RAM. They are much smaller.
"""
def __init__(self, addr_mask):
"""
:param addr_mask: Mask which defines the amount of bytes accessable
by the BRAM.
"""
self.bus = Interface(data_width=32, address_width=32, addressing="byte")
self.region = SoCRegion(size=addr_mask+1, cached=False)
self.comb += [
self.bus.cti.eq(0),
self.bus.bte.eq(0),
]
self.specials += Instance("bram",
i_clk = clk,
ADDR_MASK = addr_mask,
i_clk = ClockSignal(),
i_wb_cyc = self.bus.cyc,
i_wb_stb = self.bus.stb,
i_wb_we = self.bus.we,
@ -203,6 +301,64 @@ class BRAM(Module):
o_wb_dat_r = self.bus.dat_r,
)
class PicoRV32(Module, AutoCSR):
def __init__(self, bramwid=0x1000):
self.submodules.params = ControlLoopParameters()
self.submodules.bram = BRAM(bramwid-1)
self.submodules.bram_iface = PreemptiveInterface(2, self.submodules.bram)
self.masterbus = Interface(data_width=32, address_width=32, addressing="byte")
self.resetpin = CSRStorage(1, name="picorv32_reset", description="PicoRV32 reset")
self.trap = CSRStatus(1, name="picorv32_trap", description="Trap bit")
ic = SoCBusHandler(
standard="wishbone",
data_width=32,
address_width=32,
timeout=1e6,
bursting=False,
interconnect="shared",
interconnect_register=True,
reserved_regions={
"picorv32_null_region": SoCRegion(origin=0,size=0xFFFF, mode="ro", cached=False, decode=False)
},
)
ic.add_slave("picorv32_params", self.submodules.params, self.submodules.params.region)
ic.add_slave("picorv32_bram", self.submodules.bram_iface, self.submodules.bram.region)
ic.add_master("picorv32_master", self.masterbus)
self.specials += Instance("picorv32_wb",
o_trap = self.trap.status,
i_wb_rst_i = self.resetpin.storage,
i_wb_clk_i = ClockSignal(),
o_wbm_adr_o = self.masterbus.adr,
o_wbm_dat_o = self.masterbus.dat_r,
i_wbm_dat_i = self.masterbus.dat_w,
o_wbm_we_o = self.masterbus.we,
o_wbm_sel_o = self.masterbus.sel,
o_wbm_stb_o = self.masterbus.stb,
i_wbm_ack_i = self.masterbus.ack,
o_wbm_cyc_o = self.masterbus.cyc,
o_pcpi_valid = Signal(),
o_pcpi_insn = Signal(32),
o_pcpi_rs1 = Signal(32),
o_pcpi_rs2 = Signal(32),
i_pcpi_wr = 0,
i_pcpi_wait = 0,
i_pcpi_rd = 0,
i_pcpi_ready = 0,
i_irq = 0,
o_eoi = Signal(32),
o_trace_valid = Signal(),
o_trace_data = Signal(36),
)
# Clock and Reset Generator
# I don't know how this works, I only know that it does.
class _CRG(Module):
@ -240,10 +396,10 @@ class UpsilonSoC(SoCCore):
def add_ip(self, ip_str, ip_name):
for seg_num, ip_byte in enumerate(ip_str.split('.'),start=1):
self.add_constant(f"{ip_name}{seg_num}", int(ip_byte))
def add_bram(self, region_name):
self.bus.add_region(region_name, SoCRegion(size=0x2000, cached=False))
# TODO: special name
self.submodules.bram0 = BRAM(ClockSignal())
def add_picorv32(self):
self.submodules.picorv32 = pr = PicoRV32()
self.bus.add_region("picorv32_master_bram", pr.submodules.bram.region)
def __init__(self,
variant="a7-100",
@ -267,23 +423,11 @@ class UpsilonSoC(SoCCore):
Since Yosys doesn't support modern Verilog, only put preprocessed
(if applicable) files here.
"""
platform.add_source("rtl/spi/spi_switch_preprocessed.v")
platform.add_source("rtl/spi/spi_master_preprocessed.v")
platform.add_source("rtl/spi/spi_master_no_write_preprocessed.v")
platform.add_source("rtl/spi/spi_master_no_read_preprocessed.v")
platform.add_source("rtl/spi/spi_master_ss_preprocessed.v")
platform.add_source("rtl/spi/spi_master_ss_no_write_preprocessed.v")
platform.add_source("rtl/spi/spi_master_ss_no_read_preprocessed.v")
platform.add_source("rtl/control_loop/sign_extend.v")
platform.add_source("rtl/control_loop/intsat.v")
platform.add_source("rtl/control_loop/boothmul_preprocessed.v")
platform.add_source("rtl/control_loop/control_loop_math.v")
platform.add_source("rtl/control_loop/control_loop.v")
# platform.add_source("rtl/waveform/bram_interface_preprocessed.v")
# platform.add_source("rtl/waveform/waveform_preprocessed.v")
# when SoC cannot find a source file, it will fail with a confusing error message
platform.add_source("rtl/picorv32/picorv32.v")
platform.add_source("rtl/spi/spi_master.v")
platform.add_source("rtl/spi/spi_master_ss.v")
platform.add_source("rtl/spi/spi_master_ss_wb.v")
platform.add_source("rtl/bram/bram.v")
platform.add_source("rtl/base/base.v")
# SoCCore does not have sane defaults (no integrated rom)
SoCCore.__init__(self,
@ -330,8 +474,6 @@ class UpsilonSoC(SoCCore):
self.add_ip(remote_ip, "REMOTEIP")
self.add_constant("TFTP_SERVER_PORT", tftp_port)
self.add_bram("bram0")
# Add pins
platform.add_extension(io)
self.submodules.base = Base(ClockSignal(), self.sdram, platform)