sentinel-cpu 0.1.0b2.dev52__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sentinel_cpu/__init__.py +13 -0
- sentinel_cpu/align.py +241 -0
- sentinel_cpu/alu.py +392 -0
- sentinel_cpu/control.py +747 -0
- sentinel_cpu/csr.py +379 -0
- sentinel_cpu/datapath.py +905 -0
- sentinel_cpu/decode.py +705 -0
- sentinel_cpu/exception.py +266 -0
- sentinel_cpu/formal.py +602 -0
- sentinel_cpu/gen.py +171 -0
- sentinel_cpu/insn.py +240 -0
- sentinel_cpu/microcode.asm +540 -0
- sentinel_cpu/top.py +458 -0
- sentinel_cpu/ucodefields.py +467 -0
- sentinel_cpu/ucoderom.py +319 -0
- sentinel_cpu/version.txt +1 -0
- sentinel_cpu-0.1.0b2.dev52.dist-info/METADATA +176 -0
- sentinel_cpu-0.1.0b2.dev52.dist-info/RECORD +21 -0
- sentinel_cpu-0.1.0b2.dev52.dist-info/WHEEL +4 -0
- sentinel_cpu-0.1.0b2.dev52.dist-info/entry_points.txt +4 -0
- sentinel_cpu-0.1.0b2.dev52.dist-info/licenses/LICENSE.md +24 -0
sentinel_cpu/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""The Sentinel RISC-V CPU Package.
|
|
2
|
+
|
|
3
|
+
There is no ``__all__``; star imports are not presently supported. Users will
|
|
4
|
+
likely want to import or run one of the following modules directly:
|
|
5
|
+
|
|
6
|
+
.. testcode::
|
|
7
|
+
|
|
8
|
+
from sentinel_cpu.top import Top
|
|
9
|
+
|
|
10
|
+
::
|
|
11
|
+
|
|
12
|
+
python -m sentinel_cpu.gen --help
|
|
13
|
+
"""
|
sentinel_cpu/align.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""Components to align external data going in/out of the Sentinel Core."""
|
|
2
|
+
|
|
3
|
+
from amaranth import Signal, Module
|
|
4
|
+
from amaranth.lib.wiring import Component, In, Out
|
|
5
|
+
|
|
6
|
+
from .ucodefields import MemSel, MemExtend, MemReq, InsnFetch
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AddressAlign(Component):
|
|
10
|
+
"""Align internal address data before driving external address lines.
|
|
11
|
+
|
|
12
|
+
This :class:`~amaranth:amaranth.lib.wiring.Component` is pure combinational
|
|
13
|
+
logic that splits a 32-bit input address for a memory or I/O transfer into
|
|
14
|
+
two parts:
|
|
15
|
+
|
|
16
|
+
* A :attr:`30-bit address <wb_adr>` to/from which to write/read 32-bit
|
|
17
|
+
data. This directly drives Wishbone signal ``ADR_O``.
|
|
18
|
+
* :attr:`4 select lines <wb_sel>` which determine which bytes of the 32-bit
|
|
19
|
+
:attr:`write <WriteDataAlign.wb_dat_w>` or
|
|
20
|
+
:attr:`read data <ReadDataAlign.wb_dat_r>` are valid/meaningful for the
|
|
21
|
+
current transfer. These lines directly drive Wishbone signal ``SEL_O``.
|
|
22
|
+
|
|
23
|
+
If :attr:`insn_fetch` is asserted:
|
|
24
|
+
|
|
25
|
+
* The 30-bit address comes directly from the
|
|
26
|
+
:class:`~sentinel_cpu.datapath.ProgramCounter`.
|
|
27
|
+
* All select lines are asserted.
|
|
28
|
+
|
|
29
|
+
If :attr:`insn_fetch` is not asserted:
|
|
30
|
+
|
|
31
|
+
* The 30-bit address comes from the top 30 bits of :attr:`latched_adr`.
|
|
32
|
+
* The bottom two bits of :attr:`latched_adr` and the :attr:`mem_sel`
|
|
33
|
+
signals calculate the proper select lines to assert (all for a 32-bit
|
|
34
|
+
transfer, top two or bottom two for 16-bit transfers, and one-hot for an
|
|
35
|
+
8-bit transfer).
|
|
36
|
+
|
|
37
|
+
For space reasons, :class:`AddressAlign` does *not* qualify
|
|
38
|
+
:attr:`insn_fetch` by the *de-assertion* of
|
|
39
|
+
:data:`~sentinel_cpu.ucodefields.WriteMem`, even though an instruction
|
|
40
|
+
write transfer doesn't make really sense.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
#: In(:data:`~sentinel_cpu.ucodefields.MemReq`): If set, indicates a memory
|
|
44
|
+
#: transfer over the Wishbone bus is occurring. Outputs are qualified by
|
|
45
|
+
#: this signal.
|
|
46
|
+
mem_req: In(MemReq)
|
|
47
|
+
#: In(MemSel): Select whether to do an 8-bit, 16-bit, or 32-bit read, or
|
|
48
|
+
#: ignore.
|
|
49
|
+
mem_sel: In(MemSel)
|
|
50
|
+
#: In(:data:`~sentinel_cpu.ucodefields.InsnFetch`): If set, the current
|
|
51
|
+
#: memory read is an instruction fetch.
|
|
52
|
+
insn_fetch: In(InsnFetch)
|
|
53
|
+
#: Current value of the :class:`~sentinel_cpu.datapath.ProgramCounter`,
|
|
54
|
+
#: used to generate :attr:`wb_adr` when :attr:`insn_fetch` is asserted.
|
|
55
|
+
pc: In(30)
|
|
56
|
+
#: Registered address of the memory transfer, latched on a previous
|
|
57
|
+
#: cycle. Raw address before alignment used to generate :attr:`wb_adr` when
|
|
58
|
+
#: and :attr:`wb_sel` when :attr:`insn_fetch` is *not* asserted.
|
|
59
|
+
latched_adr: In(32)
|
|
60
|
+
#: 32-bit aligned address, which directly drives Wishbone ``ADR_O``.
|
|
61
|
+
wb_adr: Out(30)
|
|
62
|
+
#: Data select lines, which qualify which bytes in the 32-bit ``DAT_I`` and
|
|
63
|
+
#: ``DAT_O`` are valid for the current memory transfer. Directly drives
|
|
64
|
+
#: Wishbone ``SEL_O``.
|
|
65
|
+
wb_sel: Out(4)
|
|
66
|
+
|
|
67
|
+
def elaborate(self, platform): # noqa: D102
|
|
68
|
+
m = Module()
|
|
69
|
+
|
|
70
|
+
# DataPath.dat_w constantly has traffic. We only want to latch
|
|
71
|
+
# the address once per mem access, and we want it the address to be
|
|
72
|
+
# valid synchronous with ready assertion.
|
|
73
|
+
with m.If(self.mem_req):
|
|
74
|
+
with m.If(self.insn_fetch):
|
|
75
|
+
m.d.comb += [self.wb_adr.eq(self.pc),
|
|
76
|
+
self.wb_sel.eq(0xf)]
|
|
77
|
+
with m.Else():
|
|
78
|
+
m.d.comb += self.wb_adr.eq(self.latched_adr[2:])
|
|
79
|
+
|
|
80
|
+
# TODO: Misaligned accesses
|
|
81
|
+
with m.Switch(self.mem_sel):
|
|
82
|
+
with m.Case(MemSel.BYTE):
|
|
83
|
+
with m.If(self.latched_adr[0:2] == 0):
|
|
84
|
+
m.d.comb += self.wb_sel.eq(1)
|
|
85
|
+
with m.Elif(self.latched_adr[0:2] == 1):
|
|
86
|
+
m.d.comb += self.wb_sel.eq(2)
|
|
87
|
+
with m.Elif(self.latched_adr[0:2] == 2):
|
|
88
|
+
m.d.comb += self.wb_sel.eq(4)
|
|
89
|
+
with m.Else():
|
|
90
|
+
m.d.comb += self.wb_sel.eq(8)
|
|
91
|
+
with m.Case(MemSel.HWORD):
|
|
92
|
+
with m.If(self.latched_adr[1] == 0):
|
|
93
|
+
m.d.comb += self.wb_sel.eq(3)
|
|
94
|
+
with m.Else():
|
|
95
|
+
m.d.comb += self.wb_sel.eq(0xc)
|
|
96
|
+
with m.Case(MemSel.WORD):
|
|
97
|
+
m.d.comb += self.wb_sel.eq(0xf)
|
|
98
|
+
|
|
99
|
+
return m
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ReadDataAlign(Component):
|
|
103
|
+
"""Align external read data before latching internally.
|
|
104
|
+
|
|
105
|
+
This :class:`~amaranth:amaranth.lib.wiring.Component` is pure combinational
|
|
106
|
+
logic that aligns and then extends read data to 32-bits:
|
|
107
|
+
|
|
108
|
+
* If :attr:`mem_sel` is :attr:`~sentinel_cpu.ucodefields.MemSel.BYTE`, pass
|
|
109
|
+
any of the 4 bytes of :attr:`wb_dat_r` to an data extension circuit,
|
|
110
|
+
depending on :attr:`address <latched_adr>` alignment. Then
|
|
111
|
+
either zero or sign-extend the selected byte to 32-bits, depending on
|
|
112
|
+
:attr:`mem_extend`. Finally, pass the extender output to :attr:`data`.
|
|
113
|
+
|
|
114
|
+
* If :attr:`mem_sel` is :attr:`~sentinel_cpu.ucodefields.MemSel.HWORD`,
|
|
115
|
+
pass either the low 16-bits or high 16-bits of :attr:`wb_dat_r` to a data
|
|
116
|
+
extension circuit, depending on :attr:`address <latched_adr>`
|
|
117
|
+
alignment. Then, either zero or sign-extend these 16-bits to 32-bits,
|
|
118
|
+
depending on :attr:`mem_extend`. Finally, pass the extender output
|
|
119
|
+
to :attr:`data`.
|
|
120
|
+
|
|
121
|
+
* If :attr:`mem_sel` is :attr:`~sentinel_cpu.ucodefields.MemSel.WORD`, pass
|
|
122
|
+
:attr:`wb_dat_r` to :attr:`data` unaltered. The extension circuit is
|
|
123
|
+
not used.
|
|
124
|
+
|
|
125
|
+
Because I found it to be a size win, I physically implement
|
|
126
|
+
:class:`ReadDataAlign` as part of :class:`sentinel_cpu.alu.BSrcMux`; the
|
|
127
|
+
:attr:`sentinel_cpu.alu.BSrcMux.dat_r` input feeds directly into
|
|
128
|
+
:class:`ReadDataAlign`.
|
|
129
|
+
|
|
130
|
+
:class:`AddressAlign` ensures that the appropriate ``SEL_O`` lines
|
|
131
|
+
are asserted for the read.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
#: In(MemSel): Select whether to do an 8-bit, 16-bit, or 32-bit read, or
|
|
135
|
+
#: ignore.
|
|
136
|
+
mem_sel: In(MemSel)
|
|
137
|
+
#: In(MemExtend): Zero or sign-extend reads of less than 32-bits.
|
|
138
|
+
mem_extend: In(MemExtend)
|
|
139
|
+
#: Registered address from which data will be read, latched on a previous
|
|
140
|
+
#: cycle.
|
|
141
|
+
latched_adr: In(32)
|
|
142
|
+
#: Unregistered raw input data, directly from Wishbone ``DAT_I``.
|
|
143
|
+
wb_dat_r: In(32)
|
|
144
|
+
#: Aligned and extended data output.
|
|
145
|
+
data: Out(32)
|
|
146
|
+
|
|
147
|
+
def elaborate(self, platform): # noqa: D102
|
|
148
|
+
m = Module()
|
|
149
|
+
|
|
150
|
+
selected_dat = Signal.like(self.wb_dat_r)
|
|
151
|
+
|
|
152
|
+
with m.Switch(self.mem_sel):
|
|
153
|
+
with m.Case(MemSel.BYTE):
|
|
154
|
+
with m.If(self.latched_adr[0:2] == 0):
|
|
155
|
+
m.d.comb += selected_dat.eq(self.wb_dat_r[0:8])
|
|
156
|
+
with m.Elif(self.latched_adr[0:2] == 1):
|
|
157
|
+
m.d.comb += selected_dat.eq(self.wb_dat_r[8:16])
|
|
158
|
+
with m.Elif(self.latched_adr[0:2] == 2):
|
|
159
|
+
m.d.comb += selected_dat.eq(self.wb_dat_r[16:24])
|
|
160
|
+
with m.Else():
|
|
161
|
+
m.d.comb += selected_dat.eq(self.wb_dat_r[24:])
|
|
162
|
+
|
|
163
|
+
with m.If(self.mem_extend == MemExtend.SIGN):
|
|
164
|
+
m.d.comb += self.data.eq(selected_dat[0:8].as_signed())
|
|
165
|
+
with m.Else():
|
|
166
|
+
m.d.comb += self.data.eq(selected_dat[0:8])
|
|
167
|
+
with m.Case(MemSel.HWORD):
|
|
168
|
+
with m.If(self.latched_adr[1] == 0):
|
|
169
|
+
m.d.comb += selected_dat.eq(self.wb_dat_r[0:16])
|
|
170
|
+
with m.Else():
|
|
171
|
+
m.d.comb += selected_dat.eq(self.wb_dat_r[16:])
|
|
172
|
+
|
|
173
|
+
with m.If(self.mem_extend == MemExtend.SIGN):
|
|
174
|
+
m.d.comb += self.data.eq(selected_dat[0:16].as_signed())
|
|
175
|
+
with m.Else():
|
|
176
|
+
m.d.comb += self.data.eq(selected_dat[0:16])
|
|
177
|
+
with m.Case(MemSel.WORD):
|
|
178
|
+
m.d.comb += self.data.eq(self.wb_dat_r)
|
|
179
|
+
|
|
180
|
+
return m
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class WriteDataAlign(Component):
|
|
184
|
+
"""Align internal write data before sending to external peripherals.
|
|
185
|
+
|
|
186
|
+
This :class:`~amaranth:amaranth.lib.wiring.Component` is pure combinational
|
|
187
|
+
logic that aligns write data to an appropriate offset within 32-bits:
|
|
188
|
+
|
|
189
|
+
* If :attr:`mem_sel` is :attr:`~sentinel_cpu.ucodefields.MemSel.BYTE`, move
|
|
190
|
+
the low byte of :attr:`data` to one of the 4 constituent bytes of
|
|
191
|
+
:attr:`wb_dat_w`, depending on :attr:`address <latched_adr>`
|
|
192
|
+
alignment. The other bytes of :attr:`wb_dat_w` are invalid.
|
|
193
|
+
|
|
194
|
+
* If :attr:`mem_sel` is :attr:`~sentinel_cpu.ucodefields.MemSel.HWORD`, the
|
|
195
|
+
low 16-bits :attr:`data` move into either the low 16-bits or high
|
|
196
|
+
16-bits of :attr:`wb_dat_w`, depending on :attr:`address <latched_adr>`
|
|
197
|
+
alignment. The other 16-bits of :attr:`wb_dat_w` is invalid.
|
|
198
|
+
|
|
199
|
+
* If :attr:`mem_sel` is :attr:`~sentinel_cpu.ucodefields.MemSel.WORD`, pass
|
|
200
|
+
:attr:`data` to :attr:`wb_dat_w` unaltered. All bits are valid.
|
|
201
|
+
|
|
202
|
+
Since not all 32-bits of ``DAT_O`` will necessarily be valid for a given
|
|
203
|
+
write, :class:`AddressAlign` ensures that the appropriate ``SEL_O`` lines
|
|
204
|
+
are asserted for the write.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
#: In(MemSel): Select whether to do an 8-bit, 16-bit, or 32-bit write, or
|
|
208
|
+
#: ignore.
|
|
209
|
+
mem_sel: In(MemSel)
|
|
210
|
+
#: Registered address to which data will be written, latched on a previous
|
|
211
|
+
#: cycle.
|
|
212
|
+
latched_adr: In(32)
|
|
213
|
+
#: Data to be written externally, before alignment.
|
|
214
|
+
data: In(32)
|
|
215
|
+
#: Aligned data to be :data:`latched <sentinel_cpu.ucodefields.LatchData>`,
|
|
216
|
+
#: which then drives Wishbone ``DAT_O``.
|
|
217
|
+
wb_dat_w: Out(32)
|
|
218
|
+
|
|
219
|
+
def elaborate(self, platform): # noqa: D102
|
|
220
|
+
m = Module()
|
|
221
|
+
|
|
222
|
+
# TODO: Misaligned accesses
|
|
223
|
+
with m.Switch(self.mem_sel):
|
|
224
|
+
with m.Case(MemSel.BYTE):
|
|
225
|
+
with m.If(self.latched_adr[0:2] == 0):
|
|
226
|
+
m.d.comb += self.wb_dat_w[0:8].eq(self.data[0:8])
|
|
227
|
+
with m.Elif(self.latched_adr[0:2] == 1):
|
|
228
|
+
m.d.comb += self.wb_dat_w[8:16].eq(self.data[0:8])
|
|
229
|
+
with m.Elif(self.latched_adr[0:2] == 2):
|
|
230
|
+
m.d.comb += self.wb_dat_w[16:24].eq(self.data[0:8])
|
|
231
|
+
with m.Else():
|
|
232
|
+
m.d.comb += self.wb_dat_w[24:].eq(self.data[0:8])
|
|
233
|
+
with m.Case(MemSel.HWORD):
|
|
234
|
+
with m.If(self.latched_adr[1] == 0):
|
|
235
|
+
m.d.comb += self.wb_dat_w[0:16].eq(self.data[0:16])
|
|
236
|
+
with m.Else():
|
|
237
|
+
m.d.comb += self.wb_dat_w[16:].eq(self.data[0:16])
|
|
238
|
+
with m.Case(MemSel.WORD):
|
|
239
|
+
m.d.comb += self.wb_dat_w.eq(self.data)
|
|
240
|
+
|
|
241
|
+
return m
|
sentinel_cpu/alu.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"""Arithmetic Logic Unit (ALU) Components."""
|
|
2
|
+
|
|
3
|
+
from .align import ReadDataAlign
|
|
4
|
+
from .csr import MCause
|
|
5
|
+
from .ucodefields import OpType, ALUIMod, ALUOMod, ASrc, BSrc, MemSel, \
|
|
6
|
+
MemExtend, LatchA, LatchB
|
|
7
|
+
|
|
8
|
+
from amaranth import Elaboratable, Signal, Module, C, Cat
|
|
9
|
+
from amaranth.lib.wiring import Component, Signature, In, Out
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ASrcMux(Component):
|
|
13
|
+
"""Latch one of many :attr:`ALU A input <sentinel_cpu.alu.ALU.a>` sources.
|
|
14
|
+
|
|
15
|
+
The ALU does not have registered inputs; the
|
|
16
|
+
:attr:`~sentinel_cpu.alu.ASrcMux.data` output is registered and feeds
|
|
17
|
+
immediately into the ALU A input.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
#: When asserted, latch the :attr:`selected <sentinel_cpu.alu.ASrcMux.sel>`
|
|
21
|
+
#: input into :attr:`~sentinel_cpu.alu.ASrcMux.data` on the next clock
|
|
22
|
+
#: edge.
|
|
23
|
+
latch: In(1)
|
|
24
|
+
#: In(ASrc): Select input.
|
|
25
|
+
sel: In(ASrc)
|
|
26
|
+
#: Input source. Register from the
|
|
27
|
+
#: :class:`register file <sentinel_cpu.datapath.RegFile>`
|
|
28
|
+
#: whose value is currently on the read port (e.g. the read address was
|
|
29
|
+
#: supplied on the previous clock cycle).
|
|
30
|
+
gp: In(32)
|
|
31
|
+
#: Input source. :attr:`Decoded immediate <sentinel_cpu.decode.Decode.imm>`
|
|
32
|
+
#: from current instruction.
|
|
33
|
+
imm: In(32)
|
|
34
|
+
#: Input source. :attr:`Decoded immediate <sentinel_cpu.decode.Decode.imm>`
|
|
35
|
+
#: from current instruction.
|
|
36
|
+
alu: In(32)
|
|
37
|
+
#: The output. When :attr:`~sentinel_cpu.alu.ASrcMux.latch` is asserted,
|
|
38
|
+
#: the data input selected by :attr:`~sentinel_cpu.alu.ASrcMux.sel` will
|
|
39
|
+
#: appear here on the next clock cycle.
|
|
40
|
+
data: Out(32)
|
|
41
|
+
|
|
42
|
+
def elaborate(self, platform): # noqa: D102
|
|
43
|
+
m = Module()
|
|
44
|
+
|
|
45
|
+
with m.If(self.latch):
|
|
46
|
+
with m.Switch(self.sel):
|
|
47
|
+
with m.Case(ASrc.GP):
|
|
48
|
+
m.d.sync += self.data.eq(self.gp)
|
|
49
|
+
with m.Case(ASrc.IMM):
|
|
50
|
+
m.d.sync += self.data.eq(self.imm)
|
|
51
|
+
with m.Case(ASrc.ZERO):
|
|
52
|
+
m.d.sync += self.data.eq(0)
|
|
53
|
+
with m.Case(ASrc.ALU_O):
|
|
54
|
+
m.d.sync += self.data.eq(self.alu)
|
|
55
|
+
with m.Case(ASrc.FOUR):
|
|
56
|
+
m.d.sync += self.data.eq(4)
|
|
57
|
+
with m.Case(ASrc.THIRTY_ONE):
|
|
58
|
+
m.d.sync += self.data.eq(31)
|
|
59
|
+
|
|
60
|
+
return m
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class BSrcMux(Component):
|
|
64
|
+
"""Latch one of many :attr:`ALU B input <sentinel_cpu.alu.ALU.b>` sources.
|
|
65
|
+
|
|
66
|
+
The ALU does not have registered inputs; the
|
|
67
|
+
:attr:`~sentinel_cpu.alu.BSrcMux.data` output is registered and feeds
|
|
68
|
+
immediately into the ALU B input.
|
|
69
|
+
|
|
70
|
+
When requested, this module will automatically
|
|
71
|
+
:class:`move/align <sentinel_cpu.align.ReadDataAlign>` the top 16-bits
|
|
72
|
+
of the 32-bit :attr:`read data bus input <BSrcMux.dat_r>` to the bottom
|
|
73
|
+
16-bits, or any of of 3 high bytes into the bottom 8-bits. The mux will
|
|
74
|
+
latch the aligned data when :attr:`selected <sentinel_cpu.alu.BSrcMux.sel>`
|
|
75
|
+
rather than the original input data.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
#: When asserted, latch the :attr:`selected <sentinel_cpu.alu.BSrcMux.sel>`
|
|
79
|
+
#: input into :attr:`~sentinel_cpu.alu.BSrcMux.data` on the next clock
|
|
80
|
+
#: edge.
|
|
81
|
+
latch: In(1)
|
|
82
|
+
#: In(BSrc): Select input.
|
|
83
|
+
sel: In(BSrc)
|
|
84
|
+
|
|
85
|
+
#: In(MemSel): Choose which slice of the input :attr:`dat_r` appears on the
|
|
86
|
+
#: `~BSrcMux.data` output when
|
|
87
|
+
#: :attr:`selected <sentinel_cpu.alu.BSrcMux.sel>`.
|
|
88
|
+
mem_sel: In(MemSel)
|
|
89
|
+
#: In(MemExtend): When :attr:`mem_sel` is less than word width, choose
|
|
90
|
+
#: whether to sign or zero-extend :attr:`dat_r` when it's output onto
|
|
91
|
+
#: :attr:`data`.
|
|
92
|
+
mem_extend: In(MemExtend)
|
|
93
|
+
#: Contents of the internal address register latched by
|
|
94
|
+
#: :class:`~sentinel_cpu.ucodefields.LatchAdr`. Used for deciding how to
|
|
95
|
+
#: align :attr:`dat_r`.
|
|
96
|
+
data_adr: In(32)
|
|
97
|
+
|
|
98
|
+
#: Input source. Register from the
|
|
99
|
+
#: :class:`register file <sentinel_cpu.datapath.RegFile>`
|
|
100
|
+
#: whose value is currently on the read port (e.g. the read address was
|
|
101
|
+
#: supplied on the previous clock cycle).
|
|
102
|
+
gp: In(32)
|
|
103
|
+
#: Input source. :attr:`Decoded immediate <sentinel_cpu.decode.Decode.imm>`
|
|
104
|
+
#: from current instruction.
|
|
105
|
+
imm: In(32)
|
|
106
|
+
#: Input source. Current contents of the
|
|
107
|
+
#: :class:`Program Counter <sentinel_cpu.datapath.ProgramCounter>`.
|
|
108
|
+
pc: In(30)
|
|
109
|
+
#: Input source. Current contents of the *unregistered* ``DAT_I``
|
|
110
|
+
#: in :attr:`Top's Wishbone Bus <sentinel_cpu.top.Top.bus>`. Only valid
|
|
111
|
+
#: when qualified by :attr:`~sentinel_cpu.ucodefields.CondTest.MEM_VALID`.
|
|
112
|
+
#:
|
|
113
|
+
#: As an input, ``DAT_I`` is always 32-bit aligned. The mux contains
|
|
114
|
+
#: :class:`internal alignment circuitry <sentinel_cpu.align.ReadDataAlign>`
|
|
115
|
+
#: when a read of 8 or 16-bits on a less-than-32-bit alignment is
|
|
116
|
+
#: requested. When :attr:`selected <sentinel_cpu.alu.BSrcMux.sel>`, the mux
|
|
117
|
+
#: will latched this modified/aligned data into :attr:`~BSrcMux.data`.
|
|
118
|
+
dat_r: In(32)
|
|
119
|
+
#: Input source. :attr:`Decoded src_a <sentinel_cpu.decode.Decode.src_a>`
|
|
120
|
+
#: from the current instruction, which for CSR instructions is reused
|
|
121
|
+
#: for specifying 5-bit CSR immediates.
|
|
122
|
+
csr_imm: In(5)
|
|
123
|
+
#: Input source. Register from the
|
|
124
|
+
#: :class:`CSR file <sentinel_cpu.datapath.CSRFile>`
|
|
125
|
+
#: whose value is currently on the read port (e.g. the read address was
|
|
126
|
+
#: supplied on the previous clock cycle).
|
|
127
|
+
csr: In(32)
|
|
128
|
+
#: In(MCause): Input source. Current ``MCAUSE`` as determined by
|
|
129
|
+
#: :class:`~sentinel_cpu.exception.ExceptionRouter`.
|
|
130
|
+
mcause: In(MCause)
|
|
131
|
+
#: The output. When :attr:`~sentinel_cpu.alu.BSrcMux.latch` is asserted,
|
|
132
|
+
#: the data input selected by :attr:`~sentinel_cpu.alu.BSrcMux.sel` will
|
|
133
|
+
#: appear here on the next clock cycle.
|
|
134
|
+
data: Out(32)
|
|
135
|
+
|
|
136
|
+
def __init__(self):
|
|
137
|
+
self.rdata_align = ReadDataAlign()
|
|
138
|
+
super().__init__()
|
|
139
|
+
|
|
140
|
+
def elaborate(self, platform): # noqa: D102
|
|
141
|
+
m = Module()
|
|
142
|
+
|
|
143
|
+
m.submodules.rdata_align = self.rdata_align
|
|
144
|
+
|
|
145
|
+
m.d.comb += [
|
|
146
|
+
self.rdata_align.mem_sel.eq(self.mem_sel),
|
|
147
|
+
self.rdata_align.mem_extend.eq(self.mem_extend),
|
|
148
|
+
self.rdata_align.latched_adr.eq(self.data_adr),
|
|
149
|
+
self.rdata_align.wb_dat_r.eq(self.dat_r)
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
with m.If(self.latch):
|
|
153
|
+
with m.Switch(self.sel):
|
|
154
|
+
with m.Case(BSrc.GP):
|
|
155
|
+
m.d.sync += self.data.eq(self.gp)
|
|
156
|
+
with m.Case(BSrc.IMM):
|
|
157
|
+
m.d.sync += self.data.eq(self.imm)
|
|
158
|
+
with m.Case(BSrc.ONE):
|
|
159
|
+
m.d.sync += self.data.eq(1)
|
|
160
|
+
with m.Case(BSrc.PC):
|
|
161
|
+
m.d.sync += self.data.eq(Cat(C(0, 2), self.pc))
|
|
162
|
+
with m.Case(BSrc.DAT_R):
|
|
163
|
+
m.d.sync += self.data.eq(self.rdata_align.data)
|
|
164
|
+
with m.Case(BSrc.CSR_IMM):
|
|
165
|
+
m.d.sync += self.data.eq(self.csr_imm)
|
|
166
|
+
with m.Case(BSrc.CSR):
|
|
167
|
+
m.d.sync += self.data.eq(self.csr)
|
|
168
|
+
with m.Case(BSrc.MCAUSE_LATCH):
|
|
169
|
+
m.d.sync += self.data.eq(self.mcause)
|
|
170
|
+
|
|
171
|
+
return m
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class ALU(Component):
|
|
175
|
+
"""Basic Arithmetic Logic Unit.
|
|
176
|
+
|
|
177
|
+
The ALU Performs "A OP B", where "OP" is chosen by :attr:`ctrl`. More
|
|
178
|
+
operations can be synthesized from the ones directly supported by
|
|
179
|
+
:class:`sentinel_cpu.ucodefields.OpType` by using :attr:`ctrl` modifiers.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
width: int
|
|
184
|
+
Width in bits of the ALU inputs and output.
|
|
185
|
+
|
|
186
|
+
Attributes
|
|
187
|
+
----------
|
|
188
|
+
a: In(width)
|
|
189
|
+
ALU A input.
|
|
190
|
+
b: In(width)
|
|
191
|
+
ALU B input.
|
|
192
|
+
o: Out(width)
|
|
193
|
+
ALU output. Valid 1 clock cycle after inputs.
|
|
194
|
+
ctrl: In(:attr:`~sentinel_cpu.alu.ALU.ControlSignature`)
|
|
195
|
+
Choose the ALU op to perform this cycle, possibly modifying the input
|
|
196
|
+
or output. Also check if the ALU op on the previous cycle was ``0``.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
#: Signature: ALU microcode signals and useful state.
|
|
200
|
+
#:
|
|
201
|
+
#: The signature is of the form
|
|
202
|
+
#:
|
|
203
|
+
#: .. code-block::
|
|
204
|
+
#:
|
|
205
|
+
#: Signature({
|
|
206
|
+
#: "op": Out(OpType),
|
|
207
|
+
#: "imod": Out(ALUIMod),
|
|
208
|
+
#: "omod": Out(ALUOMod),
|
|
209
|
+
#: "zero": In(1)
|
|
210
|
+
#: })
|
|
211
|
+
#:
|
|
212
|
+
#: where
|
|
213
|
+
#:
|
|
214
|
+
#: .. py:attribute:: op
|
|
215
|
+
#: :type: Out(~sentinel_cpu.ucodefields.OpType)
|
|
216
|
+
#:
|
|
217
|
+
#: ALU operation to perform this cycle.
|
|
218
|
+
#:
|
|
219
|
+
#: .. py:attribute:: imod
|
|
220
|
+
#: :type: Out(~sentinel_cpu.ucodefields.ALUIMod)
|
|
221
|
+
#:
|
|
222
|
+
#: Modify the inputs :attr:`a` and :attr:`b` before doing ALU
|
|
223
|
+
#: operation.
|
|
224
|
+
#:
|
|
225
|
+
#: .. py:attribute:: omod
|
|
226
|
+
#: :type: Out(~sentinel_cpu.ucodefields.ALUOMod)
|
|
227
|
+
#:
|
|
228
|
+
#: Modify the output after doing ALU operation, but before latching
|
|
229
|
+
#: the ALU output into :attr:`o` (for next cycle).
|
|
230
|
+
#:
|
|
231
|
+
#: .. py:attribute:: zero
|
|
232
|
+
#: :type: In(1)
|
|
233
|
+
#:
|
|
234
|
+
#: Set if the current :attr:`output <o>` (i.e. the result of the ALU
|
|
235
|
+
#: operation done *last* cycle) is ``0``.
|
|
236
|
+
ControlSignature = Signature({
|
|
237
|
+
"op": Out(OpType),
|
|
238
|
+
"imod": Out(ALUIMod),
|
|
239
|
+
"omod": Out(ALUOMod),
|
|
240
|
+
"zero": In(1)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
#: Signature: Useful microcode signals concerned with routing to the ALU.
|
|
244
|
+
#:
|
|
245
|
+
#: The ALU does not directly use this
|
|
246
|
+
#: :class:`~amaranth:amaranth.lib.wiring.Signature`; it is provided for
|
|
247
|
+
#: convenience to route data sources to the :class:`ASrcMux` and
|
|
248
|
+
#: :class:`BSrcMux`.
|
|
249
|
+
#:
|
|
250
|
+
#: The signature is of the form
|
|
251
|
+
#:
|
|
252
|
+
#: .. code-block::
|
|
253
|
+
#:
|
|
254
|
+
#: Signature({
|
|
255
|
+
#: "a_src": Out(ASrc),
|
|
256
|
+
#: "b_src": Out(BSrc),
|
|
257
|
+
#: "latch_a": Out(LatchA),
|
|
258
|
+
#: "latch_b": Out(LatchB),
|
|
259
|
+
#: })
|
|
260
|
+
#:
|
|
261
|
+
#: where
|
|
262
|
+
#:
|
|
263
|
+
#: .. py:attribute:: a_src
|
|
264
|
+
#: :type: Out(~sentinel_cpu.ucodefields.ASrc)
|
|
265
|
+
#:
|
|
266
|
+
#: :class:`ASrcMux` source to pass through this clock cycle.
|
|
267
|
+
#:
|
|
268
|
+
#: .. py:attribute:: b_src
|
|
269
|
+
#: :type: Out(~sentinel_cpu.ucodefields.BSrc)
|
|
270
|
+
#:
|
|
271
|
+
#: :class:`BSrcMux` source to pass through this clock cycle.
|
|
272
|
+
#:
|
|
273
|
+
#: .. py:attribute:: latch_a
|
|
274
|
+
#: :type: Out(~sentinel_cpu.ucodefields.LatchA)
|
|
275
|
+
#:
|
|
276
|
+
#: If set, latch the :attr:`selected <a_src>` source to the
|
|
277
|
+
#: :attr:`ASrcMux output <ASrcMux.data>` this cycle.
|
|
278
|
+
#:
|
|
279
|
+
#: :type: Out(:data:`~sentinel_cpu.ucodefields.LatchA`)
|
|
280
|
+
#:
|
|
281
|
+
#: .. py:attribute:: latch_b
|
|
282
|
+
#: :type: Out(~sentinel_cpu.ucodefields.LatchB)
|
|
283
|
+
#:
|
|
284
|
+
#: If set, latch the :attr:`selected <b_src>` source to the
|
|
285
|
+
#: :attr:`BSrcMux output <BSrcMux.data>` this cycle.
|
|
286
|
+
#:
|
|
287
|
+
#: :type: Out(:data:`~sentinel_cpu.ucodefields.LatchB`)
|
|
288
|
+
RoutingSignature = Signature({
|
|
289
|
+
"a_src": Out(ASrc),
|
|
290
|
+
"b_src": Out(BSrc),
|
|
291
|
+
"latch_a": Out(LatchA),
|
|
292
|
+
"latch_b": Out(LatchB),
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
class _Unit(Elaboratable):
|
|
296
|
+
"""Wrapper class for implementing basic arithmetic/logic ops."""
|
|
297
|
+
|
|
298
|
+
def __init__(self, width, op):
|
|
299
|
+
self.a = Signal(width)
|
|
300
|
+
self.b = Signal(width)
|
|
301
|
+
self.o = Signal(width)
|
|
302
|
+
self.op = op
|
|
303
|
+
|
|
304
|
+
def elaborate(self, platform):
|
|
305
|
+
m = Module()
|
|
306
|
+
m.d.comb += self.o.eq(self.op(self.a, self.b))
|
|
307
|
+
return m
|
|
308
|
+
|
|
309
|
+
# Assumes: op is held steady for duration of op.
|
|
310
|
+
def __init__(self, width: int):
|
|
311
|
+
self.width = width
|
|
312
|
+
super().__init__(Signature({
|
|
313
|
+
"a": Out(self.width),
|
|
314
|
+
"b": Out(self.width),
|
|
315
|
+
"o": In(self.width),
|
|
316
|
+
"ctrl": Out(ALU.ControlSignature),
|
|
317
|
+
}).flip())
|
|
318
|
+
|
|
319
|
+
###
|
|
320
|
+
|
|
321
|
+
self.o_mux = Signal(width)
|
|
322
|
+
self.add = ALU._Unit(width, lambda a, b: a + b)
|
|
323
|
+
self.sub = ALU._Unit(width + 1, lambda a, b: a - b) # width + 1 for borrow bit. # noqa: E501
|
|
324
|
+
self.and_ = ALU._Unit(width, lambda a, b: a & b)
|
|
325
|
+
self.or_ = ALU._Unit(width, lambda a, b: a | b)
|
|
326
|
+
self.xor = ALU._Unit(width, lambda a, b: a ^ b)
|
|
327
|
+
self.sll = ALU._Unit(width, lambda a, _: a << 1)
|
|
328
|
+
self.srl = ALU._Unit(width, lambda a, _: a >> 1)
|
|
329
|
+
self.sar = ALU._Unit(width, lambda a, _: a.as_signed() >> 1)
|
|
330
|
+
|
|
331
|
+
def elaborate(self, platform): # noqa: D102
|
|
332
|
+
m = Module()
|
|
333
|
+
m.submodules.add = self.add
|
|
334
|
+
m.submodules.sub = self.sub
|
|
335
|
+
m.submodules.and_ = self.and_
|
|
336
|
+
m.submodules.or_ = self.or_
|
|
337
|
+
m.submodules.xor = self.xor
|
|
338
|
+
m.submodules.sll = self.sll
|
|
339
|
+
m.submodules.srl = self.srl
|
|
340
|
+
m.submodules.sal = self.sar
|
|
341
|
+
|
|
342
|
+
mod_a = Signal.like(self.a)
|
|
343
|
+
mod_b = Signal.like(self.b)
|
|
344
|
+
|
|
345
|
+
m.d.comb += [
|
|
346
|
+
mod_a.eq(self.a),
|
|
347
|
+
mod_b.eq(self.b)
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
with m.If(self.ctrl.imod == ALUIMod.INV_MSB_A_B):
|
|
351
|
+
m.d.comb += [
|
|
352
|
+
mod_a[-1].eq(~self.a[-1]),
|
|
353
|
+
mod_b[-1].eq(~self.b[-1]),
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
for submod in [self.add, self.sub, self.and_, self.or_, self.xor,
|
|
357
|
+
self.sll, self.srl, self.sar]:
|
|
358
|
+
m.d.comb += [
|
|
359
|
+
submod.a.eq(mod_a),
|
|
360
|
+
submod.b.eq(mod_b),
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
with m.Switch(self.ctrl.op):
|
|
364
|
+
with m.Case(OpType.ADD):
|
|
365
|
+
m.d.comb += self.o_mux.eq(self.add.o)
|
|
366
|
+
with m.Case(OpType.SUB):
|
|
367
|
+
m.d.comb += self.o_mux.eq(self.sub.o)
|
|
368
|
+
with m.Case(OpType.AND):
|
|
369
|
+
m.d.comb += self.o_mux.eq(self.and_.o)
|
|
370
|
+
with m.Case(OpType.OR):
|
|
371
|
+
m.d.comb += self.o_mux.eq(self.or_.o)
|
|
372
|
+
with m.Case(OpType.XOR):
|
|
373
|
+
m.d.comb += self.o_mux.eq(self.xor.o)
|
|
374
|
+
with m.Case(OpType.SLL):
|
|
375
|
+
m.d.comb += self.o_mux.eq(self.sll.o)
|
|
376
|
+
with m.Case(OpType.SRL):
|
|
377
|
+
m.d.comb += self.o_mux.eq(self.srl.o)
|
|
378
|
+
with m.Case(OpType.SRA):
|
|
379
|
+
m.d.comb += self.o_mux.eq(self.sar.o)
|
|
380
|
+
with m.Case(OpType.CMP_LTU):
|
|
381
|
+
m.d.comb += self.o_mux.eq(self.sub.o[32])
|
|
382
|
+
|
|
383
|
+
m.d.sync += self.o.eq(self.o_mux)
|
|
384
|
+
with m.If(self.ctrl.omod == ALUOMod.INV_LSB_O):
|
|
385
|
+
m.d.sync += self.o[0].eq(~self.o_mux[0])
|
|
386
|
+
with m.Elif(self.ctrl.omod == ALUOMod.CLEAR_LSB_O):
|
|
387
|
+
m.d.sync += self.o[0].eq(0)
|
|
388
|
+
|
|
389
|
+
# TODO: LSBS_2_ZERO for JALR/JAL misaligned exceptions?
|
|
390
|
+
m.d.comb += self.ctrl.zero.eq(self.o == 0)
|
|
391
|
+
|
|
392
|
+
return m
|