aboutsummaryrefslogtreecommitdiffstats
path: root/spi_master.v
blob: 82e5ee8ef65754bde8394fd38253ccf4b4350626 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/* (c) Peter McGoron 2022 v0.2
 * 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/.
 */

/* CYCLE_HALF_WAIT should take into account the setup time of the slave
 * device, and also master buffering (MISO is one cycle off to stabilize
 * 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
#(
	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.
	                               // One SCK cycle is 2*(CYCLE_HALF_WAIT + 1) clock cycles.
	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 reg mosi,
`endif
	output reg sck_wire,
	output reg finished,
	input arm
);

`ifndef SPI_MASTER_NO_READ
/* MISO is almost always an external wire, so buffer it. */
reg miso_hot = 0;
reg read_miso = 0;

always @ (posedge clk) begin
	read_miso <= miso_hot;
	miso_hot <= miso;
end
`endif

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] <= read_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

task cycle_change();
	// Stop transfer when the clock returns to its original polarity.
	if (bit_counter == WID[WID_LEN-1:0] && sck == POLARITY[0]) begin
		state <= WAIT_FINISHED;
	end else begin
		sck <= !sck;
		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

		if (CYCLE_HALF_WAIT == 0) begin
			cycle_change();
		end else begin
			state <= CYCLE_WAIT;
		end
	end
	CYCLE_WAIT: begin
		if (timer == CYCLE_HALF_WAIT) begin
			timer <= 1;
			cycle_change();
		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