diff --git a/firmware/rtl/spi/spi_master.v b/firmware/rtl/spi/spi_master.v new file mode 100644 index 0000000..cc5f01d --- /dev/null +++ b/firmware/rtl/spi/spi_master.v @@ -0,0 +1,164 @@ +/* (c) Peter McGoron 2022 + * 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_MASTER_NO_READ +spi_master_no_read +`else +`ifdef SPI_MASTER_NO_WRITE +spi_master_no_write +`else +spi_master +`endif +`endif + +#( + 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 + 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, +`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 mosi, +`endif + output sck_wire, + output finished, + input arm +); + +parameter WAIT_ON_ARM = 0; +parameter ON_CYCLE = 1; +parameter CYCLE_WAIT = 2; +parameter WAIT_FINISHED = 3; + +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; + +task idle_state(); + if (POLARITY == 0) begin + sck <= 0; + end else begin + sck <= 1; + end +`ifndef SPI_MASTER_NO_WRITE + mosi <= 0; +`endif + timer <= 0; + bit_counter <= 0; +endtask + +task read_data(); +`ifndef SPI_MASTER_NO_READ + from_slave <= from_slave << 1; + from_slave[0] <= miso; +`endif +endtask + +task write_data(); +`ifndef SPI_MASTER_NO_WRITE + mosi <= send_buf[WID-1]; + send_buf <= send_buf << 1; +`endif +endtask + +task setup_bits(); + /* 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 +`ifndef SPI_MASTER_NO_WRITE + mosi <= to_slave[WID-1]; + send_buf <= to_slave << 1; +`endif + state <= CYCLE_WAIT; + end else begin +`ifndef SPI_MASTER_NO_WRITE + send_buf <= to_slave; +`endif + state <= ON_CYCLE; + end +endtask + +always @ (posedge clk) begin + case (state) + WAIT_ON_ARM: begin + if (!arm) begin + idle_state(); + finished <= 0; + end else begin + setup_bits(); + end + end + ON_CYCLE: begin + if (sck) begin // rising edge + if (PHASE == 1) begin + write_data(); + end else begin + read_data(); + end + + if (POLARITY == 0) begin + bit_counter <= bit_counter + 1; + end + end else begin // falling edge + if (PHASE == 1) begin + read_data(); + end else begin + write_data(); + end + + if (POLARITY == 1) begin + bit_counter <= bit_counter + 1; + end + end + state <= CYCLE_WAIT; + end + CYCLE_WAIT: begin + if (timer == CYCLE_HALF_WAIT) begin + timer <= 0; + // Stop transfer when the clock returns + // to its original polarity. + if (bit_counter == WID && sck == POLARITY) begin + state <= WAIT_FINISHED; + end else begin + state <= ON_CYCLE; + sck <= !sck; + end + end else begin + timer <= timer + 1; + end + end + WAIT_FINISHED: begin + finished <= 1; + idle_state(); + if (!arm) begin + state <= WAIT_ON_ARM; + end + end + endcase +end + +endmodule diff --git a/firmware/rtl/spi/spi_master_no_write.v b/firmware/rtl/spi/spi_master_no_write.v new file mode 100644 index 0000000..31f8b5c --- /dev/null +++ b/firmware/rtl/spi/spi_master_no_write.v @@ -0,0 +1,3 @@ +`define SPI_MASTER_NO_WRITE +/* verilator lint_off DECLFILENAME */ +`include "spi_master.v" diff --git a/firmware/soc.py b/firmware/soc.py index 2630fda..5ffffc4 100644 --- a/firmware/soc.py +++ b/firmware/soc.py @@ -139,31 +139,66 @@ io = [ IOStandard("LVCMOS33")) ] -class DACThroughGPIO(Module, AutoCSR): - def __init__(self, pins): - self._miso = CSRStatus(1, description="Master In, Slave Out (Status)") - # Read as [MSB ... LSB] - self._ctrl = CSRStorage(3, description="SS, SCK, MOSI (Control)") - self._pins = pins +class SPIMaster(Module, AutoCSR): + def __init__(self, wid, clk, pins): + self.pins = pins - self.comb += self._miso.status.eq(self._pins.miso) + self.from_slave = CSRStatus(wid, description="Data from slave (Status)") + self.to_slave = CSRStorage(wid, description="Data to slave (Control)") + self.finished = CSRStatus(1, description="Finished transmission (Status)") + self.arm = CSRStorage(1, description="Initiate transmission (Status)") + self.ss = CSRStorage(1, description="Slave Select (active high)") - self.comb += self._pins.ss.eq(~self._ctrl.storage[2]) - self.comb += self._pins.mosi.eq(self._ctrl.storage[1]) - self.comb += self._pins.sck.eq(self._ctrl.storage[0]) + self.comb += self.pins.ss.eq(~self.ss.storage) -class ADCThroughGPIO(Module, AutoCSR): - def __init__(self, pins): - self._pins = pins - self._conv = CSRStorage(1, description="Conversion Signal (Control)") - self._sck = CSRStorage(1, description="Serial Clock (Control)") - self._sdo = CSRStatus(1, description="Serial Data Output (Status)") + import math - self.comb += self._pins.conv.eq(self._conv.storage) - self.comb += self._pins.sck.eq(self._sck.storage) + self.specials += Instance("spi_master", + p_WID=wid, + p_WID_LEN=math.ceil(math.log2(wid)), + p_CYCLE_HALF_WAIT = 3, # 3 + 2 = 5, total sck = 10 cycles + p_TIMER_LEN = 3, + p_POLARITY = 0, + p_PHASE = 1, + i_clk = clk, + o_from_slave = self.from_slave.status, + i_miso = self.pins.miso, + i_to_slave = self.to_slave.storage, + o_mosi = self.pins.mosi, + o_sck_wire = self.pins.sck, + o_finished = self.finished.status, + i_arm = self.arm.storage + ) - self.comb += self._sdo.status.eq(self._pins.sdo) +class SPIMasterReadOnly(Module, AutoCSR): + def __init__(self, wid, clk, pins): + self.pins = pins + self.from_slave = CSRStatus(wid, description="Data from slave (Status)description=") + self.finished = CSRStatus(1, description="Finished transmission (Status)description=") + self.arm = CSRStorage(1, description="Initiate transmission (Status)description=") + self.conv = CSRStorage(1, description="Conversion (active high)description=") + + self.comb += self.pins.conv.eq(self.conv.storage) + + import math + + self.specials += Instance("spi_master_no_write", + p_WID=wid, + p_WID_LEN=math.ceil(math.log2(wid)), + p_CYCLE_HALF_WAIT = 1, # 1 + 2 = 3, total sck = 6 cycles + p_TIMER_LEN = 3, + p_POLARITY = 1, + p_PHASE = 0, + i_clk = clk, + o_from_slave = self.from_slave.status, + i_miso = self.pins.sdo, + o_sck_wire = self.pins.sck, + o_finished = self.finished.status, + i_arm = self.arm.storage + ) + +# Clock and Reset Generator class _CRG(Module): def __init__(self, platform, sys_clk_freq, with_dram=True, with_rst=True): self.rst = Signal() @@ -200,6 +235,8 @@ class CryoSNOM1SoC(SoCCore): sys_clk_freq = int(100e6) platform = board_spec.Platform(variant=variant, toolchain="symbiflow") self.submodules.crg = _CRG(platform, sys_clk_freq, True) + platform.add_source("rtl/spi/spi_master.v") + platform.add_source("rtl/spi/spi_master_no_write.v") # SoCCore does not have sane defaults (no integrated rom) SoCCore.__init__(self, @@ -230,16 +267,16 @@ class CryoSNOM1SoC(SoCCore): l2_cache_size = 8192 ) self.submodules.ethphy = LiteEthPHYMII( - clock_pads = self.platform.request("eth_clocks"), - pads = self.platform.request("eth")) + clock_pads = platform.request("eth_clocks"), + pads = platform.request("eth")) self.add_ethernet(phy=self.ethphy, dynamic_ip=True) # Add the DAC and ADC pins as GPIO. They will be used directly # by Zephyr. platform.add_extension(io) for i in range(0,8): - setattr(self.submodules, f"dac{i}", DACThroughGPIO(platform.request("dac", i))) - setattr(self.submodules, f"adc{i}", ADCThroughGPIO(platform.request("adc", i))) + setattr(self.submodules, f"dac{i}", SPIMaster(24, ClockSignal(), platform.request("dac", i))) + setattr(self.submodules, f"adc{i}", SPIMasterReadOnly(24, ClockSignal(), platform.request("adc", i))) def main(): soc = CryoSNOM1SoC("a7-35")