CIRCT 24.0.0git
Loading...
Searching...
No Matches
mmio.py
Go to the documentation of this file.
1# ===- mmio.py - MMIO register-file component -----------------------------===//
2#
3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4# See https://llvm.org/LICENSE.txt for license information.
5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6#
7# ===----------------------------------------------------------------------===//
8#
9# A timing-friendly MMIO register file built on `esi.MMIO.read_write`. It owns
10# the MMIO request bundle, presents automatically-numbered read-only,
11# read-write, and write-only registers to the host, and exposes a FF-bounded
12# command surface so the BSP MMIO mux only ever sees registered outputs.
13#
14# ===----------------------------------------------------------------------===//
15
16import pycde.esi as esi
17from pycde import AppID, Clock, Input, Module, Output, Reset, generator
18from pycde.constructs import ControlReg, Mux, Wire
19from pycde.module import modparams
20from pycde.types import Array, Bits, Channel, StructType, UInt
21from pycde.support import clog2
22
23# A presented MMIO write command. Unlike ``esi.MMIOReadWriteCmdType`` there is
24# no redundant ``write`` field -- every command on this surface is a write.
25MMIOWriteCmdType = StructType([
26 ("offset", UInt(32)),
27 ("data", Bits(64)),
28])
29
30
31@modparams
32def MmioRegistry(num_ro: int, num_rw: int, num_wo: int):
33 """MMIO register file with automatically-numbered registers (RO < RW < WO)
34 addressed at an 8-byte stride (``index = offset >> 3``).
35
36 Numbering::
37
38 [0, num_ro) -> read-only (RO)
39 [num_ro, num_ro + num_rw) -> read-write (RW)
40 [num_ro + num_rw, num_ro+num_rw+num_wo) -> write-only (WO)
41
42 The RO and RW registers ("read registers", ``num_ro + num_rw`` of them) are
43 held internally. The client updates any of them via the per-read-register
44 ``read_reg_ce`` / ``read_reg_data`` arrays and observes their current value
45 on ``read_reg_value`` (all length ``num_ro + num_rw``, indexed RO-first then
46 RW). A host write to an RW register additionally stores the written data into
47 that register directly; a host write takes priority over a coincident client
48 update of the same register.
49
50 Reads -- and the read-back value returned for a write -- respond with the
51 selected register's value, or all-ones (-1) if the offset selects a WO
52 register or is out of bounds.
53
54 Writes to RW/WO registers are registered and presented to the client on
55 ``write_cmd_r`` / ``write_cmd_xact_r`` (writes to RO offsets or out-of-bounds
56 offsets are dropped). ``mmio_write_we`` derives per-offset write strobes from
57 these.
58
59 The MMIO bundle is created inside this submodule's ``@generator``, so the
60 request's AppID is anchored at this instance's hierarchical position.
61
62 Ports:
63 read_reg_ce : Input Array(Bits(1), num_ro + num_rw)
64 read_reg_data : Input Array(Bits(64), num_ro + num_rw)
65 read_reg_value : Output Array(Bits(64), num_ro + num_rw)
66 write_cmd_r : Output MMIOWriteCmdType (last presented write)
67 write_cmd_xact_r : Output Bits(1) (1-cycle pulse on a presented write)
68 """
69 num_read = num_ro + num_rw
70 num_total = num_ro + num_rw + num_wo
71 assert num_read >= 1, "MmioRegistry needs at least one read register."
72
73 class MmioRegistryImpl(Module):
74 clk = Clock()
75 rst = Reset()
76
77 read_reg_ce = Input(Array(Bits(1), num_read))
78 read_reg_data = Input(Array(Bits(64), num_read))
79 read_reg_value = Output(Array(Bits(64), num_read))
80
81 write_cmd_r = Output(MMIOWriteCmdType)
82 write_cmd_xact_r = Output(Bits(1))
83
84 @generator
85 def construct(ports):
86 clk = ports.clk
87 rst = ports.rst
88
89 mmio_bundle = esi.MMIO.read_write(appid=AppID("cmd"))
90
91 cmd_chan_wire = Wire(Channel(esi.MMIOReadWriteCmdType))
92 cmd_ready_wire = Wire(Bits(1))
93 cmd, cmd_valid = cmd_chan_wire.unwrap(cmd_ready_wire)
94
95 resp_pending_wire = Wire(Bits(1))
96 resp_data_r = Wire(Bits(64))
97 resp_chan, resp_ready = Channel(Bits(64)).wrap(resp_data_r,
98 resp_pending_wire)
99 resp_xact = resp_pending_wire & resp_ready
100 cmd_xact = cmd_valid & ~resp_pending_wire
101 cmd_ready_wire.assign(~resp_pending_wire)
102 resp_pending_wire.assign(
103 ControlReg(
104 clk=clk,
105 rst=rst,
106 asserts=[cmd_xact],
107 resets=[resp_xact],
108 name="resp_pending",
109 ))
110
111 # Register index = MMIO byte offset >> 3 (8-byte stride).
112 idx = cmd.offset.as_bits()[3:].as_uint()
113 idx_w = idx.type.width
114
115 def is_index(i):
116 return idx == UInt(idx_w)(i)
117
118 # Read registers (RO first, then RW). RW registers also auto-store a
119 # host write (host takes priority over a coincident client update).
120 read_values = []
121 for i in range(num_read):
122 if i >= num_ro:
123 host_we = cmd_xact & cmd.write & is_index(i)
124 reg_ce = host_we | ports.read_reg_ce[i]
125 reg_data = Mux(host_we, ports.read_reg_data[i], cmd.data)
126 else:
127 reg_ce = ports.read_reg_ce[i]
128 reg_data = ports.read_reg_data[i]
129 read_values.append(
130 reg_data.reg(
131 clk=clk,
132 rst=rst,
133 rst_value=Bits(64)(0),
134 ce=reg_ce,
135 name=f"read_reg_{i}",
136 ))
137 read_values_arr = Array(Bits(64), num_read)(read_values)
138 ports.read_reg_value = read_values_arr
139
140 # Read / write-response value: the selected read register, or -1 for a
141 # WO register or an out-of-bounds offset.
142 sel_read_value = read_values_arr[idx.as_bits(clog2(num_read))]
143 resp_sel = Mux(
144 idx < UInt(idx_w)(num_read),
145 Bits(64)(2**64 - 1),
146 sel_read_value,
147 )
148 resp_data_r.assign(
149 resp_sel.reg(
150 clk=clk,
151 rst=rst,
152 rst_value=Bits(64)(0),
153 ce=cmd_xact,
154 name="resp_data_r",
155 ))
156
157 # Present RW/WO writes to the client; drop RO / out-of-bounds writes.
158 presented = (cmd_xact & cmd.write & (idx >= UInt(idx_w)(num_ro)) &
159 (idx < UInt(idx_w)(num_total)))
160 write_cmd = MMIOWriteCmdType({"offset": cmd.offset, "data": cmd.data})
161 ports.write_cmd_r = write_cmd.reg(clk=clk,
162 rst=rst,
163 ce=presented,
164 name="write_cmd_r")
165 ports.write_cmd_xact_r = presented.reg(clk=clk,
166 rst=rst,
167 rst_value=Bits(1)(0),
168 name="write_cmd_xact_r")
169
170 mmio_rw_cmd_chan = mmio_bundle.unpack(data=resp_chan)["cmd"]
171 cmd_chan_wire.assign(mmio_rw_cmd_chan)
172
173 return MmioRegistryImpl
174
175
176def mmio_write_we(mmio, offset: int):
177 """Registered 1-cycle write-enable strobe for a host write to ``offset``.
178
179 Only pulses for *presented* writes (RW/WO registers); asserts the cycle
180 after the write is accepted. Use as a register ``ce`` or a start pulse.
181 ``offset`` is the register's byte offset (``index << 3``).
182 """
183 return mmio.write_cmd_xact_r & (mmio.write_cmd_r.offset == UInt(32)(offset))
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))