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.
@@ -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