Skip to content

Commit 8af5ee0

Browse files
committed
[dpi, verilator]: Upgrade SPI DPI implementation
* use a new protocol, incompatible with the legacy implementation * the new protocol is much simpler and exposes existing SPI DPI device signals: * host-to-device: SCK, SDI, CS * device-to-host: SDO, SDO_EN * there is no longer a limitation (32 bits) on transaction size * use an ASCII protocol to ease debugging (no overhead) * use environment variable to select whether to generate log files, and their kind: * `M` for "monitor" file, which are very verbose * `P` for "protocol" file, more compact to trace communication with SPI host * tested with JEDEC_ID, SFDP, PAGE_PROGRAM, READ, and HW config commands. Signed-off-by: Emmanuel Blot <[email protected]>
1 parent 241b623 commit 8af5ee0

File tree

2 files changed

+105
-166
lines changed

2 files changed

+105
-166
lines changed

hw/dv/dpi/spidpi/spidpi.c

Lines changed: 103 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
33
// SPDX-License-Identifier: Apache-2.0
44

5+
#include <stdbool.h>
6+
57
#ifdef __linux__
68
#include <linux/limits.h>
79
#include <pty.h>
@@ -12,7 +14,6 @@
1214
#include <assert.h>
1315
#include <errno.h>
1416
#include <fcntl.h>
15-
#include <limits.h>
1617
#include <stdio.h>
1718
#include <stdlib.h>
1819
#include <string.h>
@@ -22,71 +23,34 @@
2223
#include <unistd.h>
2324

2425
#include "spidpi.h"
25-
#ifdef VERILATOR
2626
#include "verilator_sim_ctrl.h"
27-
#endif
2827

2928
// This holds the necessary SPI state.
30-
#define MAX_TRANSACTION 4
29+
3130
struct spidpi_ctx {
3231
int loglevel;
33-
char ptyname[64];
3432
int host;
3533
int device;
34+
FILE *log_file;
3635
FILE *mon_file;
37-
char mon_pathname[PATH_MAX];
3836
void *mon;
39-
int tick;
40-
int cpol;
41-
int cpha;
42-
int msbfirst; // shift direction
43-
int nout;
44-
int bout;
45-
int nin;
46-
int bin;
47-
int din;
48-
int nmax;
49-
char driving;
50-
int state;
51-
char buf[MAX_TRANSACTION];
37+
unsigned tick;
38+
uint8_t rx;
39+
uint8_t tx;
40+
bool push_tx;
5241
};
5342

54-
// SPI Host States
55-
#define SP_IDLE 0
56-
#define SP_CSFALL 1
57-
#define SP_DMOVE 2
58-
#define SP_LASTBIT 3
59-
#define SP_CSRISE 4
60-
#define SP_FINISH 99
61-
62-
// Enable this define to stop tracing at cycle 4
63-
// and resume at the first SPI packet
64-
// #define CONTROL_TRACE
65-
6643
void *spidpi_create(const char *name, int mode, int loglevel) {
67-
int i;
6844
struct spidpi_ctx *ctx =
6945
(struct spidpi_ctx *)calloc(1, sizeof(struct spidpi_ctx));
7046
assert(ctx);
7147

7248
ctx->loglevel = loglevel;
73-
ctx->mon = monitor_spi_init(mode);
7449
ctx->tick = 0;
75-
ctx->msbfirst = 1;
76-
ctx->nmax = MAX_TRANSACTION;
77-
ctx->nin = 0;
78-
ctx->nout = 0;
79-
ctx->bout = 0;
80-
ctx->state = SP_IDLE;
81-
/* mode is CPOL << 1 | CPHA
82-
* cpol = 0 --> external clock matches internal
83-
* cpha = 0 --> drive on internal falling edge, capture on rising
84-
*/
85-
ctx->cpol = ((mode == 0) || (mode == 2)) ? 0 : 1;
86-
ctx->cpha = ((mode == 1) || (mode == 3)) ? 1 : 0;
87-
/* CPOL = 1 for clock idle high */
88-
ctx->driving = P2D_CSB | ((ctx->cpol) ? P2D_SCK : 0);
50+
ctx->rx = P2D_CSB;
51+
ctx->tx = 0;
8952
char cwd[PATH_MAX];
53+
char ptyname[64];
9054
char *cwd_rv;
9155
cwd_rv = getcwd(cwd, sizeof(cwd));
9256
assert(cwd_rv != NULL);
@@ -98,162 +62,136 @@ void *spidpi_create(const char *name, int mode, int loglevel) {
9862
rv = openpty(&ctx->host, &ctx->device, 0, &tty, 0);
9963
assert(rv != -1);
10064

101-
rv = ttyname_r(ctx->device, ctx->ptyname, 64);
65+
rv = ttyname_r(ctx->device, ptyname, 64);
10266
assert(rv == 0 && "ttyname_r failed");
10367

10468
int cur_flags = fcntl(ctx->host, F_GETFL, 0);
10569
assert(cur_flags != -1 && "Unable to read current flags.");
10670
int new_flags = fcntl(ctx->host, F_SETFL, cur_flags | O_NONBLOCK);
10771
assert(new_flags != -1 && "Unable to set FD flags");
10872

73+
// might be worth getting rid of this blahblahblah
10974
printf(
11075
"\n"
11176
"SPI: Created %s for %s. Connect to it with any terminal program, e.g.\n"
11277
"$ screen %s\n"
11378
"NOTE: a SPI transaction is run for every 4 characters entered.\n",
114-
ctx->ptyname, name, ctx->ptyname);
79+
ptyname, name, ptyname);
80+
81+
char *spi_log = getenv("VERILATOR_SPI_LOG");
82+
83+
// check if some logging files are requested
84+
if (spi_log) {
85+
// log SPI as ASCII-art signals (very verbose)
86+
char log_pathname[PATH_MAX];
87+
if (strchr(spi_log, 'M')) {
88+
// keep original name to avoid breaking some existing tool
89+
rv = snprintf(log_pathname, PATH_MAX, "%s/%s.log", cwd, name);
90+
assert(rv < PATH_MAX && rv > 0);
91+
ctx->mon_file = fopen(log_pathname, "wt");
92+
if (ctx->mon_file == NULL) {
93+
fprintf(stderr, "SPI: Unable to open file at %s: %s\n", log_pathname,
94+
strerror(errno));
95+
free(ctx);
96+
return NULL;
97+
}
98+
setlinebuf(ctx->mon_file);
99+
ctx->mon = monitor_spi_init(mode);
100+
printf(
101+
"SPI: Monitor output file created at %s. Works well with tail:\n"
102+
"$ tail -f %s\n",
103+
log_pathname, log_pathname);
104+
}
115105

116-
rv = snprintf(ctx->mon_pathname, PATH_MAX, "%s/%s.log", cwd, name);
117-
assert(rv <= PATH_MAX && rv > 0);
118-
ctx->mon_file = fopen(ctx->mon_pathname, "w");
119-
if (ctx->mon_file == NULL) {
120-
fprintf(stderr, "SPI: Unable to open file at %s: %s\n", ctx->mon_pathname,
121-
strerror(errno));
122-
return NULL;
106+
// log SPI PTY protocol (communication with peer)
107+
if (strchr(spi_log, 'P')) {
108+
rv = snprintf(log_pathname, PATH_MAX, "%s/%s.pty.log", cwd, name);
109+
assert(rv < PATH_MAX && rv > 0);
110+
ctx->log_file = fopen(log_pathname, "wt");
111+
if (!ctx->log_file) {
112+
fprintf(stderr, "SPI: Unable to open file at %s: %s\n", log_pathname,
113+
strerror(errno));
114+
setlinebuf(ctx->log_file);
115+
} else {
116+
printf("SPI: PTY output file created at %s.\n", log_pathname);
117+
}
118+
}
123119
}
124-
// more useful for tail -f
125-
setlinebuf(ctx->mon_file);
126-
printf(
127-
"SPI: Monitor output file created at %s. Works well with tail:\n"
128-
"$ tail -f %s\n",
129-
ctx->mon_pathname, ctx->mon_pathname);
120+
121+
ctx->push_tx = false;
130122

131123
return (void *)ctx;
132124
}
133125

134-
char spidpi_tick(void *ctx_void, const svLogicVecVal *d2p_data) {
126+
__attribute__((noinline)) uint8_t spidpi_tick2(void *ctx_void,
127+
const svLogicVecVal *d2p_data) {
135128
struct spidpi_ctx *ctx = (struct spidpi_ctx *)ctx_void;
136129
assert(ctx);
137-
int d2p = d2p_data->aval;
138130

139-
// Will tick at the host clock
140-
ctx->tick++;
131+
ctx->tx = d2p_data->aval & (D2P_SDO | D2P_SDO_EN);
141132

142-
#ifdef VERILATOR
143-
#ifdef CONTROL_TRACE
144-
if (ctx->tick == 4) {
145-
VerilatorSimCtrl::GetInstance().TraceOff();
133+
if (ctx->push_tx) {
134+
/* add '0' in ASCII so that it easier to debug */
135+
uint8_t out_byte = 0x30 | ctx->tx;
136+
if (ctx->log_file) {
137+
fprintf(ctx->log_file, "@%u tx:%01x\n", ctx->tick, ctx->tx);
138+
fflush(ctx->log_file);
139+
}
140+
ctx->push_tx = false;
141+
(void)write(ctx->host, &out_byte, sizeof(out_byte));
146142
}
147-
#endif
148-
#endif
149143

150-
monitor_spi(ctx->mon, ctx->mon_file, ctx->loglevel, ctx->tick, ctx->driving,
151-
d2p);
144+
// Will tick at the host clock
145+
ctx->tick++;
152146

153-
if (ctx->state == SP_IDLE) {
154-
int n = read(ctx->host, &(ctx->buf[ctx->nin]), ctx->nmax - ctx->nin);
155-
if (n == -1) {
156-
if (errno != EAGAIN) {
157-
fprintf(stderr, "Read on SPI FIFO gave %s\n", strerror(errno));
158-
}
159-
} else {
160-
ctx->nin += n;
161-
if (ctx->nin == ctx->nmax) {
162-
ctx->nout = 0;
163-
ctx->nin = 0;
164-
ctx->bout = ctx->msbfirst ? 0x80 : 0x01;
165-
ctx->bin = ctx->msbfirst ? 0x80 : 0x01;
166-
ctx->din = 0;
167-
ctx->state = SP_CSFALL;
168-
#ifdef VERILATOR
169-
#ifdef CONTROL_TRACE
170-
VerilatorSimCtrl::GetInstance().TraceOn();
171-
#endif
172-
#endif
173-
}
174-
}
147+
if (ctx->mon && ctx->mon_file) {
148+
monitor_spi(ctx->mon, ctx->mon_file, ctx->loglevel, ctx->tick, ctx->rx,
149+
ctx->tx);
175150
}
151+
176152
// SPI clock toggles every 4th tick (i.e. freq=primary_frequency/8)
177-
if ((ctx->tick & 3) || (ctx->state == SP_IDLE)) {
178-
return ctx->driving;
153+
if (ctx->tick & 3) {
154+
return ctx->rx;
179155
}
180156

181-
// Only get here on sck edges when active
182-
int internal_sck = (ctx->tick & 4) ? 1 : 0;
183-
int set_sck = (internal_sck ? P2D_SCK : 0);
184-
if (ctx->cpol) {
185-
set_sck ^= P2D_SCK;
186-
}
157+
uint8_t in_byte;
187158

188-
if (internal_sck == ctx->cpha) {
189-
// host driving edge (falling for mode 0)
190-
switch (ctx->state) {
191-
case SP_DMOVE:
192-
// SCLK low, CSB low
193-
ctx->driving =
194-
set_sck | (ctx->buf[ctx->nout] & ctx->bout) ? P2D_SDI : 0;
195-
ctx->bout = (ctx->msbfirst) ? ctx->bout >> 1 : ctx->bout << 1;
196-
if ((ctx->bout & 0xff) == 0) {
197-
ctx->bout = ctx->msbfirst ? 0x80 : 0x01;
198-
ctx->nout++;
199-
if (ctx->nout == ctx->nmax) {
200-
ctx->state = SP_LASTBIT;
201-
}
202-
}
203-
break;
204-
case SP_LASTBIT:
205-
ctx->state = SP_CSRISE;
206-
// fallthrough
207-
default:
208-
ctx->driving = set_sck | (ctx->driving & ~P2D_SCK);
209-
break;
210-
}
211-
} else {
212-
// host other edge (rising for mode 0)
213-
switch (ctx->state) {
214-
// Read input data (opposite edge to sending)
215-
// both DMOVE and LASTBIT states are data moving ones!
216-
case SP_DMOVE:
217-
case SP_LASTBIT:
218-
ctx->din = ctx->din | ((d2p & D2P_SDO) ? ctx->bin : 0);
219-
ctx->bin = (ctx->msbfirst) ? ctx->bin >> 1 : ctx->bin << 1;
220-
if (ctx->bin == 0) {
221-
int rv = write(ctx->host, &(ctx->din), 1);
222-
assert(rv == 1 && "write() failed.");
223-
ctx->bin = (ctx->msbfirst) ? 0x80 : 0x01;
224-
ctx->din = 0;
225-
}
226-
ctx->driving = set_sck | (ctx->driving & ~P2D_SCK);
227-
break;
228-
case SP_CSFALL:
229-
// CSB low, drive SDI to first bit
230-
ctx->driving =
231-
(set_sck | (ctx->buf[ctx->nout] & ctx->bout) ? P2D_SDI : 0);
232-
ctx->state = SP_DMOVE;
233-
break;
234-
case SP_CSRISE:
235-
// CSB high, clock stopped
236-
ctx->driving = P2D_CSB;
237-
ctx->state = SP_IDLE;
238-
break;
239-
case SP_FINISH:
240-
#ifdef VERILATOR
241-
VerilatorSimCtrl::GetInstance().RequestStop(true);
242-
#endif
243-
break;
244-
default:
245-
ctx->driving = set_sck | (ctx->driving & ~P2D_SCK);
246-
break;
159+
int n = read(ctx->host, &in_byte, sizeof(in_byte));
160+
if (n == -1) {
161+
if (errno != EAGAIN) {
162+
fprintf(stderr, "Read on SPI FIFO gave %s\n", strerror(errno));
247163
}
164+
return ctx->rx;
248165
}
249-
return ctx->driving;
166+
167+
/* filter MSB so that ASCII-encoded digit can be accepted (for debug) */
168+
in_byte &= P2D_SCK | P2D_CSB | P2D_SDI;
169+
ctx->rx = in_byte;
170+
ctx->push_tx = true;
171+
172+
if (ctx->log_file) {
173+
fprintf(ctx->log_file, "@%u rx:%01x\n", ctx->tick, ctx->rx);
174+
fflush(ctx->log_file);
175+
}
176+
177+
return ctx->rx;
178+
}
179+
180+
uint8_t spidpi_tick(void *ctx_void, const svLogicVecVal *d2p_data) {
181+
return spidpi_tick2(ctx_void, d2p_data);
250182
}
251183

252184
void spidpi_close(void *ctx_void) {
253185
struct spidpi_ctx *ctx = (struct spidpi_ctx *)ctx_void;
254186
if (!ctx) {
255187
return;
256188
}
257-
fclose(ctx->mon_file);
189+
if (ctx->mon_file) {
190+
fclose(ctx->mon_file);
191+
}
192+
if (ctx->log_file) {
193+
fprintf(ctx->log_file, "%u ticks\n");
194+
fclose(ctx->log_file);
195+
}
258196
free(ctx);
259197
}

hw/dv/dpi/spidpi/spidpi.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#ifndef OPENTITAN_HW_DV_DPI_SPIDPI_SPIDPI_H_
66
#define OPENTITAN_HW_DV_DPI_SPIDPI_SPIDPI_H_
77

8+
#include <stdint.h>
89
#include <svdpi.h>
910

1011
#ifdef __cplusplus
@@ -21,7 +22,7 @@ extern "C" {
2122
#define P2D_SDI 0x4
2223

2324
void *spidpi_create(const char *name, int mode, int loglevel);
24-
char spidpi_tick(void *ctx_void, const svLogicVecVal *d2p_data);
25+
uint8_t spidpi_tick(void *ctx_void, const svLogicVecVal *d2p_data);
2526
void spidpi_close(void *ctx_void);
2627

2728
// monitor

0 commit comments

Comments
 (0)