Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a4e8419

Browse files
committedNov 30, 2018
wip
1 parent f333817 commit a4e8419

File tree

2 files changed

+576
-0
lines changed

2 files changed

+576
-0
lines changed
 

‎software/glasgow/applet/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ def run_test():
347347
from .jtag_pinout import JTAGPinoutApplet
348348
from .jtag_svf import JTAGSVFApplet
349349
from .jtag_xc9500 import JTAGXC9500Applet
350+
from .lpc_monitor import LPCMonitorApplet
350351
from .nand_flash import NANDFlashApplet
351352
from .program_ice40 import ProgramICE40Applet
352353
from .selftest import SelfTestApplet
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,575 @@
1+
# Ref: Intel® Low Pin Count (LPC) Interface Specification
2+
# Document Number: 251289-001
3+
4+
# The LPC bus makes a monitor fairly hard to implement by using variable-length frames in three
5+
# different ways.
6+
# 1. The LPC frame field *width* is variable. Address and data width can only by determined
7+
# by completely parsing the frame header.
8+
# 2. The LPC frame field *position* is variable, even within the same frame type. Synchronization
9+
# cycle insertion means the frame needs to be completely parsed in order to determine where
10+
# the data or acknowledgement is located.
11+
# 3. The LPC frame *extent* is variable. LFRAME# is only asserted at frame start, and nothing
12+
# is asserted at frame end, so frame end can only be determined by completely parsing
13+
# the frame.
14+
#
15+
# It would not be possible to stream raw LPC frames to the host as-is (logic analyzer style,
16+
# with {LAD,LFRAME} occupying low bits and framing occupying high bits) because revAB uses 30 MHz
17+
# FIFO clock and the PCI clock is 33.33 MHz. Thus, frames need to be processed, and thus, frames
18+
# need to be parsed completely.
19+
#
20+
# If frames are parsed, there is a choice between streaming raw frames to host after adding
21+
# an appropriate header with length and flags, or streaming processed frames.
22+
# * Streaming raw frames has the advantage of simplicity and reducing the amount of bugs in less
23+
# used frame formats. However, it introduces a latency of up to 1 frame, which requires
24+
# buffering.
25+
# * Streaming processed frames has the advantage of directly streaming frames to the host with
26+
# minimal latency. However, there are very many frame formats, and no easy way to test them,
27+
# since x86 systems do not allow generating arbitrary LPC frames.
28+
#
29+
# An additional consideration is that if frame filtering is to be performed in gateware, buffering
30+
# of up to 1 frame (or at least up to 1 frame header, which is almost as bad) will have to be
31+
# performed anyway. Since there could be a very large amount of non-POST traffic (e.g. when reading
32+
# the BIOS image over LPC), a POST card use case would greatly benefit from filtering in terms
33+
# of reducing buffer overruns.
34+
#
35+
# As a result, the following design is used for the LPC monitor:
36+
# * All supported frames are parsed completely, in the LPC clock domain.
37+
# * Two FIFOs are used. Data FIFO buffers raw (4-bit) LAD values. Control FIFO buffers packet
38+
# sizes and header flags like "buffer overrun".
39+
# * Each time a payload nibble is parsed, a word is pushed into data FIFO. Nibbles such as TAR
40+
# or 2nd and later repeat of SYNC are not pushed. This limits the data FIFO size to either:
41+
# a) at most 32 nibbles, if 128-byte FWH reads are not supported, or
42+
# b) at most 273 nibbles, if 128-byte FWH reads are supported.
43+
# (Values taken from LPC specification and include all TAR and SYNC cycles. Actual FIFO depth
44+
# requirements are slightly lower because of that.)
45+
# * Each time a complete packet is parsed and either accepted or rejected, the running count
46+
# of the nibbles in the data FIFO is pushed into the control FIFO.
47+
# - Note: the control FIFO size has to be the same as the data FIFO size, to accomodate corner
48+
# cases such as a back-to-back sequence of aborts on the bus, which is effectively a series
49+
# of frames that each have size 1.
50+
# * On the read end of control and data FIFO, a simple packetizer either skips all nibbles of
51+
# rejected packets, or else downconverts nibbles of accepted packets to bytes and pushes them
52+
# into the host FIFO.
53+
#
54+
# This design also has a desirable property that the LPC state machine can be reset at will
55+
# (such as during bus aborts) and yet the framing does not need to be self-synchronizing, because
56+
# the packetizer is independent from the LPC FSM. At the same time, even a self-synchronizing
57+
# encoding would not be able to reliably transfer aborted frames.
58+
59+
import logging
60+
import asyncio
61+
import argparse
62+
import struct
63+
from bitarray import bitarray
64+
from migen import *
65+
from migen.genlib.resetsync import AsyncResetSynchronizer
66+
from migen.genlib.fifo import SyncFIFOBuffered
67+
from migen.genlib.fsm import FSM
68+
69+
from .. import *
70+
from ...arch.lpc import *
71+
from ...gateware.pads import *
72+
73+
74+
class LPCBus(Module):
75+
def __init__(self, pads):
76+
self.lad = pads.lad_t
77+
self.lframe = ~pads.lframe_t.i
78+
self.lreset = ~pads.lreset_t.i
79+
self.lclk = pads.lclk_t.i
80+
81+
82+
class LPCMonitorFramePacketizer(Module):
83+
def __init__(self, in_fifo, depth=512):
84+
self.submodules.ctrl_fifo = ctrl_fifo = SyncFIFOBuffered(width=11, depth=depth)
85+
self.submodules.data_fifo = data_fifo = SyncFIFOBuffered(width=4, depth=depth)
86+
87+
ctrl = Record([
88+
("accept", 1),
89+
("overflow", 1),
90+
("length", 9),
91+
])
92+
data = Signal(4)
93+
94+
self.submodules.fsm = FSM()
95+
self.fsm.act("IDLE",
96+
If(in_fifo.writable & ctrl_fifo.readable,
97+
NextValue(ctrl.raw_bits(), ctrl_fifo.dout),
98+
ctrl_fifo.re.eq(1),
99+
If(ctrl_fifo.dout[0],
100+
NextState("HEADER-1")
101+
).Else(
102+
NextState("SKIP")
103+
)
104+
)
105+
)
106+
self.fsm.act("HEADER-1",
107+
If(in_fifo.writable,
108+
in_fifo.we.eq(1),
109+
in_fifo.din.eq(ctrl.length[8:] | (ctrl.overflow << 7)),
110+
NextState("HEADER-2")
111+
)
112+
)
113+
self.fsm.act("HEADER-2",
114+
If(in_fifo.writable,
115+
in_fifo.we.eq(1),
116+
in_fifo.din.eq(ctrl.length[:8]),
117+
NextState("DATA-HIGH")
118+
)
119+
)
120+
self.fsm.act("DATA-HIGH",
121+
If(ctrl.length == 0,
122+
NextState("IDLE")
123+
).Else(
124+
If(in_fifo.writable & data_fifo.readable,
125+
data_fifo.re.eq(1),
126+
NextValue(data, data_fifo.dout),
127+
NextValue(ctrl.length, ctrl.length - 1),
128+
NextState("DATA-LOW")
129+
)
130+
)
131+
)
132+
self.fsm.act("DATA-LOW",
133+
If(ctrl.length == 0,
134+
If(in_fifo.writable,
135+
in_fifo.we.eq(1),
136+
in_fifo.din.eq(data << 4),
137+
NextState("IDLE")
138+
)
139+
).Else(
140+
If(in_fifo.writable & data_fifo.readable,
141+
data_fifo.re.eq(1),
142+
in_fifo.we.eq(1),
143+
in_fifo.din.eq(Cat(data_fifo.dout, data)),
144+
NextValue(ctrl.length, ctrl.length - 1),
145+
NextState("DATA-HIGH")
146+
)
147+
)
148+
)
149+
self.fsm.act("SKIP",
150+
If(ctrl.length == 0,
151+
NextState("IDLE")
152+
).Else(
153+
If(data_fifo.readable,
154+
data_fifo.re.eq(1),
155+
NextValue(ctrl.length, ctrl.length - 1),
156+
)
157+
)
158+
)
159+
160+
161+
class LPCMonitorFrameParser(Module):
162+
def __init__(self, bus, ctrl_fifo, data_fifo):
163+
lad_r = Signal.like(bus.lad.i)
164+
self.sync.lpc += lad_r.eq(bus.lad.i)
165+
166+
push = Signal()
167+
accept = Signal()
168+
reject = Signal()
169+
170+
length = Signal(max=data_fifo.depth)
171+
self.comb += [
172+
data_fifo.we.eq(push),
173+
data_fifo.din.eq(lad_r),
174+
]
175+
self.sync += [
176+
length.eq(Mux(ctrl_fifo.we & ctrl_fifo.writable, 0, length) +
177+
(push & data_fifo.writable))
178+
]
179+
180+
overrun = Signal()
181+
self.comb += [
182+
ctrl_fifo.we.eq(accept | reject),
183+
ctrl_fifo.din.eq(Cat(accept, overrun, length)),
184+
]
185+
self.sync += [
186+
If(~data_fifo.writable | ~ctrl_fifo.writable,
187+
overrun.eq(1)
188+
).Elif(ctrl_fifo.we,
189+
overrun.eq(0)
190+
)
191+
]
192+
193+
index = Signal(max=8)
194+
trans = Record([
195+
("start", 4),
196+
("dir", 1),
197+
("cyctype", 2),
198+
("size", 2),
199+
("addr", 32),
200+
("sync", 4),
201+
])
202+
203+
self.submodules.fsm = ResetInserter()(FSM())
204+
self.comb += self.fsm.reset.eq(bus.lframe)
205+
self.sync += accept.eq((bus.lframe | self.fsm.after_entering("DONE")) & (length != 0))
206+
self.fsm.act("START",
207+
push.eq(~bus.lframe),
208+
NextValue(trans.start, lad_r),
209+
Case(lad_r, {
210+
START_TARGET: NextState("TARGET-CYCTYPE-DIR"),
211+
STOP_ABORT: NextState("DONE"),
212+
"default": NextState("DONE"),
213+
})
214+
)
215+
self.fsm.act("DONE",
216+
NextState("DONE")
217+
)
218+
# Target memory or I/O cycles
219+
self.fsm.act("TARGET-CYCTYPE-DIR",
220+
push.eq(1),
221+
NextValue(trans.dir, lad_r[1]),
222+
NextValue(trans.cyctype, lad_r[2:3]),
223+
NextState("TARGET-ADDR"),
224+
Case(lad_r[2:3], {
225+
CYCTYPE_IO: NextValue(index, 3),
226+
CYCTYPE_MEM: NextValue(index, 7),
227+
"default": NextState("DONE")
228+
})
229+
)
230+
self.fsm.act("TARGET-ADDR",
231+
push.eq(1),
232+
NextValue(trans.addr, Cat(lad_r, trans.addr)),
233+
NextValue(index, index - 1),
234+
If(index == 0,
235+
If(trans.dir == DIR_READ,
236+
NextValue(index, 1),
237+
NextState("TARGET-RD-TAR")
238+
).Else(
239+
NextValue(index, 1),
240+
NextState("TARGET-WR-DATA")
241+
)
242+
)
243+
)
244+
# - Target read sub-cycles
245+
self.fsm.act("TARGET-RD-TAR",
246+
NextValue(index, index - 1),
247+
If(index == 0,
248+
NextState("TARGET-RD-SYNC")
249+
)
250+
)
251+
self.fsm.act("TARGET-RD-SYNC",
252+
NextValue(trans.sync, lad_r),
253+
If((lad_r == SYNC_SHORT_WAIT) | (lad_r == SYNC_LONG_WAIT),
254+
push.eq(index == 0),
255+
NextValue(index, 1),
256+
).Elif((lad_r == SYNC_READY) | (lad_r == SYNC_ERROR),
257+
push.eq(1),
258+
NextValue(index, 1),
259+
NextState("TARGET-RD-DATA")
260+
).Else(
261+
push.eq(1),
262+
NextState("DONE")
263+
)
264+
)
265+
self.fsm.act("TARGET-RD-DATA",
266+
push.eq(1),
267+
If(index == 0,
268+
NextState("DONE")
269+
)
270+
)
271+
# - Target write sub-cycles
272+
self.fsm.act("TARGET-WR-DATA",
273+
push.eq(1),
274+
If(index == 0,
275+
NextValue(index, 1),
276+
NextState("TARGET-WR-TAR")
277+
)
278+
)
279+
self.fsm.act("TARGET-WR-TAR",
280+
NextValue(index, index - 1),
281+
If(index == 0,
282+
NextValue(index, 0),
283+
NextState("TARGET-WR-SYNC")
284+
)
285+
)
286+
self.fsm.act("TARGET-WR-SYNC",
287+
NextValue(trans.sync, lad_r),
288+
If((lad_r == SYNC_SHORT_WAIT) | (lad_r == SYNC_LONG_WAIT),
289+
push.eq(index == 0),
290+
NextValue(index, 1),
291+
).Else(
292+
push.eq(1),
293+
NextState("DONE")
294+
)
295+
)
296+
297+
298+
class LPCMonitorSubtarget(Module):
299+
def __init__(self, pads, reset, make_in_fifo):
300+
self.submodules.bus = bus = LPCBus(pads)
301+
302+
self.clock_domains.cd_lpc = ClockDomain()
303+
self.comb += self.cd_lpc.clk.eq(bus.lclk)
304+
self.specials += AsyncResetSynchronizer(self.cd_lpc, reset)
305+
306+
self.submodules.packetizer = ClockDomainsRenamer("lpc")(
307+
LPCMonitorFramePacketizer(make_in_fifo(self.cd_lpc), depth=127))
308+
self.submodules.parser = ClockDomainsRenamer("lpc")(
309+
LPCMonitorFrameParser(self.bus, self.packetizer.ctrl_fifo, self.packetizer.data_fifo))
310+
311+
312+
class LPCFrameParser:
313+
def __init__(self, frame_bytes, length):
314+
self.length = length
315+
self.offset = 0
316+
self.frame = bitarray()
317+
self.frame.frombytes(bytes(frame_bytes))
318+
self.items = []
319+
self.warn = False
320+
321+
def _add(self, item, *args):
322+
self.items.append(item.format(*args))
323+
324+
def _err(self, item, *args):
325+
self._add("err=" + item, *args)
326+
raise ValueError("unrecognized")
327+
328+
def _get_nibbles(self, count):
329+
if self.offset + count > self.length:
330+
self._err("truncated")
331+
332+
start = self.offset * 4
333+
subframe = self.frame[start:start + count * 4]
334+
value = subframe.tobytes()
335+
self.offset += count
336+
return value
337+
338+
def _get_nibble(self):
339+
value, = self._get_nibbles(1)
340+
return value >> 4
341+
342+
def _get_byte(self):
343+
value, = struct.unpack(">B", self._get_nibbles(2))
344+
return value
345+
346+
def _get_word(self):
347+
value, = struct.unpack(">H", self._get_nibbles(4))
348+
return value
349+
350+
def _get_dword(self):
351+
value, = struct.unpack(">L", self._get_nibbles(8))
352+
return value
353+
354+
def _parse_cyctype_dir(self):
355+
cyctype_dir = self._get_nibble()
356+
357+
cyctype = (cyctype_dir & 0b1100) >> 2
358+
if cyctype == CYCTYPE_IO:
359+
self._add("io")
360+
elif cyctype == CYCTYPE_MEM:
361+
self._add("mem")
362+
elif cyctype == CYCTYPE_DMA:
363+
self._add("dma")
364+
else:
365+
self._add("type={:2b}", cyctype)
366+
self._err("illegal")
367+
368+
cycdir = (cyctype_dir & 0b0010) >> 1
369+
if cycdir == DIR_READ:
370+
self._add("rd")
371+
elif cycdir == DIR_WRITE:
372+
self._add("wr")
373+
374+
if cyctype == CYCTYPE_IO:
375+
address = self._get_word()
376+
self._add("addr={:04x}", address)
377+
elif cyctype == CYCTYPE_MEM:
378+
address = self._get_dword()
379+
self._add("addr={:08x}", address)
380+
else:
381+
self._err("illegal")
382+
383+
return cyctype, cycdir
384+
385+
def _parse_sync(self):
386+
sync1 = self._get_nibble()
387+
if sync1 in (SYNC_SHORT_WAIT, SYNC_LONG_WAIT):
388+
if sync1 == SYNC_SHORT_WAIT:
389+
self._add("sync=short")
390+
if sync1 == SYNC_LONG_WAIT:
391+
self._add("sync=long")
392+
sync2 = self._get_nibble()
393+
else:
394+
sync2 = sync1
395+
396+
if sync2 == SYNC_READY:
397+
self._add("sync=ok")
398+
elif sync2 == SYNC_READY_MORE:
399+
self._add("sync=more")
400+
elif sync2 == SYNC_ERROR:
401+
self._add("sync=err")
402+
elif sync2 == 0b1111:
403+
# Not an actual code, but a consequence of SYNC always following TAR, and LAD being
404+
# driven to 1111 during TAR.
405+
self._add("sync=1111")
406+
self._err("timeout")
407+
else:
408+
self._add("sync={:04b}", sync2)
409+
self._err("illegal")
410+
411+
return sync2
412+
413+
def _parse_target(self):
414+
cyctype, cycdir = self._parse_cyctype_dir()
415+
if cycdir == DIR_READ:
416+
sync = self._parse_sync()
417+
data = self._get_byte()
418+
self._add("data={:02x}", data)
419+
if cycdir == DIR_WRITE:
420+
data = self._get_byte()
421+
self._add("data={:02x}", data)
422+
sync = self._parse_sync()
423+
424+
def parse_frame(self):
425+
start = self._get_nibble()
426+
if start == START_TARGET:
427+
self._add("target")
428+
self._parse_target()
429+
430+
elif start in (START_BUS_MASTER_0, START_BUS_MASTER_1):
431+
self._add("master")
432+
self._add("idx={:d}", start & 1)
433+
self._err("unimplemented")
434+
435+
elif start in (START_FW_MEM_READ, START_FW_MEM_WRITE):
436+
self._add("fw-mem")
437+
if start == START_FW_MEM_READ:
438+
self._add("read")
439+
if start == START_FW_MEM_WRITE:
440+
self._add("write")
441+
self._err("unimplemented")
442+
443+
elif start == STOP_ABORT:
444+
self.warn = True
445+
self._add("abort")
446+
447+
else:
448+
self._add("start={:04b}", start)
449+
self._err("illegal")
450+
451+
residue = self.length - self.offset
452+
if residue > 0:
453+
self.warn = True
454+
self._add("residue=<{:s}>", self._get_nibbles(residue).hex()[:residue])
455+
456+
457+
class LPCMonitorInterface:
458+
def __init__(self, interface, logger):
459+
self.lower = interface
460+
self._logger = logger
461+
self._level = logging.DEBUG if self._logger.name == __name__ else logging.TRACE
462+
463+
def _log(self, message, *args):
464+
self._logger.log(self._level, "LPC: " + message, *args)
465+
466+
async def monitor(self):
467+
header, = struct.unpack(">H", await self.lower.read(2))
468+
overflow = header >> 15
469+
length = header & 0x7fff
470+
if overflow:
471+
self._logger.warning("FIFO overflow")
472+
if length == 0:
473+
return
474+
475+
packet = await self.lower.read((length + 1) // 2)
476+
self._log("data=<%s>", packet.hex()[:length])
477+
478+
parser = LPCFrameParser(packet, length)
479+
level = logging.INFO
480+
try:
481+
parser.parse_frame()
482+
if parser.warn:
483+
level = logging.WARN
484+
except ValueError as e:
485+
level = logging.ERROR
486+
self._logger.log(level, "LPC: %s", " ".join(parser.items))
487+
488+
489+
class LPCMonitorApplet(GlasgowApplet, name="lpc-monitor"):
490+
preview = True
491+
logger = logging.getLogger(__name__)
492+
help = "monitor transactions on the Intel LPC bus"
493+
description = """
494+
TBD
495+
"""
496+
497+
__pin_sets = ("lad",)
498+
__pins = ("lframe", "lreset", "lclk")
499+
500+
@classmethod
501+
def add_build_arguments(cls, parser, access):
502+
super().add_build_arguments(parser, access)
503+
504+
access.add_pin_argument(parser, "lclk", default=True)
505+
access.add_pin_set_argument(parser, "lad", width=4, default=True)
506+
access.add_pin_argument(parser, "lframe", default=True)
507+
access.add_pin_argument(parser, "lreset", default=True)
508+
509+
def build(self, target, args):
510+
self.mux_interface = iface = target.multiplexer.claim_interface(self, args)
511+
subtarget = LPCMonitorSubtarget(
512+
pads=iface.get_pads(args, pin_sets=self.__pin_sets, pins=self.__pins),
513+
reset=iface.reset,
514+
make_in_fifo=lambda cd: iface.get_in_fifo(clock_domain=cd),
515+
)
516+
iface.submodules += subtarget
517+
target.platform.add_period_constraint(subtarget.bus.lclk, 30)
518+
519+
async def run(self, device, args):
520+
iface = await device.demultiplexer.claim_interface(self, self.mux_interface, args)
521+
lpc_iface = LPCMonitorInterface(iface, self.logger)
522+
return lpc_iface
523+
524+
@classmethod
525+
def add_interact_arguments(cls, parser):
526+
p_operation = parser.add_subparsers(dest="operation", metavar="OPERATION")
527+
528+
async def interact(self, device, args, lpc_iface):
529+
# for _ in range(128):
530+
# while True:
531+
# await lpc_iface.monitor()
532+
with open("lpc.bin", "wb") as f:
533+
f.write(await lpc_iface.lower.read(1000000, hint=1000000))
534+
535+
# -------------------------------------------------------------------------------------------------
536+
537+
class LPCMonitorAppletTool(GlasgowAppletTool, applet=LPCMonitorApplet):
538+
help = "TBD"
539+
description = "TBD"
540+
541+
@classmethod
542+
def add_arguments(cls, parser):
543+
parser.add_argument(
544+
"file", metavar="FILE", type=argparse.FileType("rb"),
545+
help="read LPC frames from FILE")
546+
547+
async def run(self, args):
548+
while True:
549+
header, = struct.unpack(">H", args.file.read(2))
550+
overflow = header >> 15
551+
length = header & 0x7fff
552+
if overflow:
553+
self.logger.warning("FIFO overflow")
554+
if length == 0:
555+
continue
556+
557+
packet = args.file.read((length + 1) // 2)
558+
self.logger.debug("data=<%s>", packet.hex()[:length])
559+
560+
parser = LPCFrameParser(packet, length)
561+
level = logging.INFO
562+
try:
563+
parser.parse_frame()
564+
if parser.warn:
565+
level = logging.WARN
566+
except ValueError as e:
567+
level = logging.ERROR
568+
self.logger.log(level, "LPC: %s", " ".join(parser.items))
569+
570+
# -------------------------------------------------------------------------------------------------
571+
572+
class LPCMonitorAppletTestCase(GlasgowAppletTestCase, applet=LPCMonitorApplet):
573+
@synthesis_test
574+
def test_build(self):
575+
self.assertBuilds()

0 commit comments

Comments
 (0)
Please sign in to comment.