(no commit message)
[libreriscv.git] / 3d_gpu / architecture / decoder.mdwn
1 # Decoder
2
3 <http://bugs.libre-riscv.org/show_bug.cgi?id=186>
4
5 The decoder is in charge of translating the POWER instruction stream into operations that can be handled by our backend. It will have an extra input bit, set via a MSR, that will switch on GPU instructions.
6
7 Source code: <https://git.libre-riscv.org/?p=soc.git;a=tree;f=src/soc/decoder;hb=HEAD>
8
9 # POWER
10
11 The decoder has been written in python, to parse straight CSV files and other information taken directly from the Power ISA Standards PDF files. This significantly reduces the possibility of manual transcription errors and greatly reduces code size. Based on Anton Blanchard's excellent microwatt design, these tables are in [[openpower/isatables]] which includes links to download the csv files.
12
13 The top level decoder object recursively drops through progressive levels of case statement groups, covering additional portions of the incoming instruction bits. More on this technique - for which python and nmigen were *specifically* and strategically chosen - is outlined here <http://lists.libre-riscv.org/pipermail/libre-riscv-dev/2020-March/004882.html>
14
15 The PowerDecoder2, on encountering for example an ADD
16 operation, needs to know whether Rc=0/1, whether OE=0/1, whether
17 RB is to be read, whether an immediate is to be read and so on.
18 With all of this information being specified in the CSV files, on
19 a per-instruction basis, it is simply a matter of expanding that
20 information out into a data structure called Decode2ToExecute1Type.
21 From there it becomes easily possible for other parts of the processor
22 to take appropriate action.
23
24 * [Decode2ToExecute1Type](https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/decoder/decode2execute1.py;hb=HEAD)
25
26 ## Link to Function Units
27
28 The Decoder (PowerDecode2) knows which registers are needed, however what
29 it does not know is:
30
31 * which Register file ports to connect to (this is defined by regspecs)
32 * the order of those regfile ports (again: defined by regspecs)
33
34 Neither do the Phase-aware Function Units (derived from MultiCompUnit)
35 themselves know anything about the PowerDecoder, and they certainly
36 do not know when a given instruction will need to tell *them* to read
37 RA, or RB. For example: negation of RA only requires one operand,
38 where add RA, RB requires two. Who tells whom that information, when
39 the ALU's job is simply to add, and the Decoder's job is simply to decode?
40
41 This is where a special function called "rdflags()" comes into play.
42 rdflags works closely in conjunction with regspecs and the PowerDecoder2,
43 in each Function Unit's "pipe\_data.py" file. It defines the flags that
44 determine, from current instruction, whether the Function Unit actually
45 *wants* any given Register Read Ports activated or not.
46
47 That dynamically-determined information will then actively disable
48 (or allow) Register file Read requests (rd.req) on a per-port basis.
49
50 Example:
51
52 class ALUInputData(IntegerData):
53 regspec = [('INT', 'ra', '0:63'), # RA
54 ('INT', 'rb', '0:63'), # RB/immediate
55 ('XER', 'xer_so', '32'), # XER bit 32: SO
56 ('XER', 'xer_ca', '34,45')] # XER bit 34/45: CA/CA32
57
58 This shows us that, for the ALU pipeline, it expects two INTEGER
59 operands (RA and RB) both 64-bit, and it expects XER SO, CA and CA32
60 bits. However this information - as to which operands are required -
61 is *dynamic*.
62
63 Continuing from the OP_ADD example, where inspection of the CSV files
64 (or the ISA tables) shows that we optionally need xer_so (OE=1),
65 optionally need xer_ca (Rc=1), and even optionally need RB (add with
66 immediate), we begin to understand that a dynamic system linking the
67 PowerDecoder2 information to the Function Units is needed. This is
68 where power\_regspec\_map.py comes into play.
69
70 def regspec_decode_read(e, regfile, name):
71 if regfile == 'INT':
72 # Int register numbering is *unary* encoded
73 if name == 'ra': # RA
74 return e.read_reg1.ok, 1<<e.read_reg1.data
75 if name == 'rb': # RB
76 return e.read_reg2.ok, 1<<e.read_reg2.data
77
78 Here we can see that, for INTEGER registers, if the Function Unit
79 has a connection (an incoming operand) named "RA", the tuple returned
80 contains two crucial pieces of information:
81
82 1. The field from PowerDecoder2 which tells us if RA is even actually
83 required by this (decoded) instruction
84 2. The INTEGER Register file read port activation signal (its read-enable
85 line-activation) which, if sent to the INTEGER Register file, will
86 request the actual register required by this current (decoded)
87 instruction.
88
89 Thus we have the *dynamic* information - not hardcoded in RTL but
90 specified in *python* - encoding both if (first item of tuple) and
91 what (second item of tuple) each Function Unit receives, and this
92 for each and every operand. A corresponding process exists for write,
93 as well.
94
95 * [[architecture/regfile]]
96 * [CompUnits](https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/fu/compunits/compunits.py;hb=HEAD)
97 * Example [ALU pipe_data.py specification](https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/fu/alu/pipe_data.py;hb=HEAD)
98 * [power_regspec_map.py](https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/decoder/power_regspec_map.py;hb=HEAD)
99
100 ## Fixed point instructions
101
102 - addi, addis, mulli - fairly straightforward - extract registers and immediate and translate to the appropriate op
103 - addic, addic., subfic - similar to above, but now carry needs to be saved somewhere
104 - add[o][.], subf[o][.], adde\*, subfe\*, addze\*, neg\*, mullw\*, divw\* - These are more fun. They need to set the carry (if . is present) and overflow (if o is present) flags, as well as taking in the carry flag for the extended versions.
105 - addex - uses the overflow flag as a carry in, and if CY is set to 1, sets overflow like it would carry.
106 - cmp, cmpi - sets bits of the selected comparison result register based on whether the comparison result was greater than, less than, or equal to
107 - andi., ori, andis., oris, xori, xoris - similar to above, though the and versions set the flags in CR0
108 - and\*, or\*, xor\*, nand\*, eqv\*, andc\*, orc\* - similar to the register-register arithmetic instructions above
109
110 # Decoder internals
111
112 The Decoder uses a class called PowerOp which get instantiated
113 for every instruction. PowerOp class instantiation has member signals
114 whose values get set respectively for each instruction.
115
116 We use Python Enums to help with common decoder values.
117 Below is the POWER add insruction.
118
119 | opcode | unit | internal op | in1 | in2 | in3 | out | CR in | CR out | inv A | inv out | cry in | cry out | ldst len | BR | sgn ext | upd | rsrv | 32b | sgn | rc | lk | sgl pipe | comment | form |
120 |--------------|------|-------------|-----|-----|------|-----|-------|--------|-------|---------|--------|---------|----------|----|---------|-----|------|-----|-----|----|----|----------|---------|------|
121 | 0b0100001010 | ALU | OP_ADD | RA | RB | NONE | RT | 0 | 0 | 0 | 0 | ZERO | 0 | NONE | 0 | 0 | 0 | 0 | 0 | 0 | RC | 0 | 0 | add | XO |
122
123 Here is an example of a toy multiplexer that sets various fields in the
124 PowerOP signal class to the correct values for the add instruction when
125 select is set equal to 1. This should give you a feel for how we work with
126 enums and PowerOP.
127
128 from nmigen import Module, Elaboratable, Signal, Cat, Mux
129 from soc.decoder.power_enums import (Function, Form, InternalOp,
130 In1Sel, In2Sel, In3Sel, OutSel, RC, LdstLen,
131 CryIn, get_csv, single_bit_flags,
132 get_signal_name, default_values)
133 from soc.decoder.power_fields import DecodeFields
134 from soc.decoder.power_fieldsn import SigDecode, SignalBitRange
135 from soc.decoder.power_decoder import PowerOp
136
137 class Op_Add_Example(Elaboratable):
138 def __init__(self):
139 self.select = Signal(reset_less=True)
140 self.op_add = PowerOp()
141
142 def elaborate(self, platform):
143 m = Module()
144 op_add = self.op_add
145
146 with m.If(self.select == 1):
147 m.d.comb += op_add.function_unit.eq(Function.ALU)
148 m.d.comb += op_add.form.eq(Form.XO)
149 m.d.comb += op_add.internal_op.eq(InternalOp.OP_ADD)
150 m.d.comb += op_add.in1_sel.eq(In1Sel.RA)
151 m.d.comb += op_add.in2_sel.eq(In2Sel.RB)
152 m.d.comb += op_add.in3_sel.eq(In3Sel.NONE)
153 m.d.comb += op_add.out_sel.eq(OutSel.RT)
154 m.d.comb += op_add.rc_sel.eq(RC.RC)
155 m.d.comb += op_add.ldst_len.eq(LdstLen.NONE)
156 m.d.comb += op_add.cry_in.eq(CryIn.ZERO)
157
158 return m
159
160 from nmigen.back import verilog
161 verilog_file = "op_add_example.v"
162 top = Op_Add_Example()
163 f = open(verilog_file, "w")
164 verilog = verilog.convert(top, name='top', strip_internal_attrs=True,
165 ports=top.op_add.ports())
166 f.write(verilog)
167 print(f"Verilog Written to: {verilog_file}")
168
169 The [actual POWER9 Decoder](https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/decoder/power_decoder2.py;hb=HEAD)
170 uses this principle, in conjunction with reading the information shown
171 in the table above from CSV files (as opposed to hardcoding them in
172 python source). These [[CSV files|openpower/isatables]],
173 being machine-readable in a wide variety
174 of programming languages, are conveniently available for use by
175 other projects well beyond just this SOC.
176
177 This also demonstrates one of the design aspects taken in this project: to
178 *combine* the power of python's full capabilities in order to create
179 advanced dynamically generated HDL, rather than (as done with MyHDL)
180 limit python code to a subset of its full capabilities.
181
182 The CSV Files are loaded by
183 [power_decoder.py](https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/decoder/power_decoder.py;hb=HEAD)
184 and are used to construct a hierarchical cascade of switch statements. The original code came from
185 [microwatt](https://github.com/antonblanchard/microwatt/blob/master/decode1.vhdl)
186 where the original hardcoded cascade can be seen.
187
188 The docstring for power_decoder.py gives more details: each level in the hierarchy, just as in the original decode1.vhdl, will take slices of the instruction bitpattern, match against it, and if successful will continue with further subdecoders until a line is met that contains the required Operand Information (a PowerOp) exactly as shown at the top of this page.
189
190 In this way, different sections of the instruction are successively decoded (major opcode, then minor opcode, then sub-patterns under those) until the required instruction is fully recognised, and the hierarchical cascade of switch patterns results in a flat interpretation being produced that is useful internally.
191
192 # second explanation / walkthrough
193
194 we (manually) extracted the pseudo-code from the v3.0B specification
195 (took 2 days):
196 <https://git.libre-soc.org/?p=libreriscv.git;a=blob;f=openpower/isa/fixedlogical.mdwn;hb=HEAD>
197
198 then wrote a parser and language translator (aka compiler) to convert
199 those code-fragments to python:
200 <https://git.libre-soc.org/?p=soc.git;a=tree;f=src/soc/decoder/pseudo;hb=HEAD>
201
202 then went to a lot of trouble over the course of several months to
203 co-simulate them, update them, and make them accurate according to the
204 actual spec:
205 <https://git.libre-soc.org/?p=libreriscv.git;a=blob;f=openpower/isa/fixedarith.mdwn;h=470a833ca2b8a826f5511c4122114583ef169e55;hb=HEAD#l721>
206
207 and created a fully-functioning python-based OpenPOWER ISA simulator:
208 <https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/decoder/isa/caller.py;hb=HEAD>
209
210 there is absolutely no reason why this language-translator (aka compiler)
211 here
212 <https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/decoder/pseudo/parser.py;hb=HEAD>
213
214 should not be joined by another compiler, targetting c for use inside
215 the linux kernel or, another compiler which auto-generates c++ for use
216 inside power-gem5, such that this:
217 <https://github.com/power-gem5/gem5/blob/cae53531103ebc5bccddf874db85f2659b64000a/src/arch/power/isa/decoder.isa#L1214>
218
219 becomes an absolute breeze to update.
220
221 note that we maintain a decoder which is based on Microwatt: we extracted
222 microwatt's decode1.vhdl into CSV files, and parse them in python as
223 hierarchical recursive data structures:
224 <https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/decoder/power_decoder.py;hb=HEAD>
225
226 where the actual CSV files that it reads are here:
227 <https://git.libre-soc.org/?p=libreriscv.git;a=tree;f=openpower/isatables;hb=HEAD>
228
229 this is then combined with *another* table that was extracted from the
230 OpenPOWER v3.0B PDF:
231 <https://git.libre-soc.org/?p=libreriscv.git;a=blob;f=openpower/isatables/fields.text;hb=HEAD>
232
233 (the parser for that recognises "vertical bars" as being
234 field-separators):
235 <https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/decoder/power_fields.py;hb=HEAD>
236
237 and FINALLY - and this is about the only major piece of code that
238 actually involves any kind of manual code - again it is based on Microwatt
239 decode2.vhdl - we put everything together to turn a binary opcode into
240 "something that needs to be executed":
241 <https://git.libre-soc.org/?p=soc.git;a=blob;f=src/soc/decoder/power_decoder2.py;hb=HEAD>
242
243 so our OpenPOWER simulator is actually based on:
244
245 * machine-readable CSV files
246 * machine-readable Field-Form files
247 * machine-readable spec-accurate pseudocode files
248
249 the only reason we haven't used those to turn it into HDL is because
250 doing so is a massive research project, where a first pass would be
251 highly likely to generate sub-optimal HDL
252