RISC-V using only the 16-bit compressed instructions ... is it usable? - Page 1
Forum section: electronics / general

author: I wanted a rude username
author_rank: Frequent Contributor
timestamp: January 02, 2021, 01:59:00 am
content: Maybe I'm drunk on fermented Christmas leftovers, but RISC-V usingonlythe 16-bit compressed instructionsappears to be Turing complete. I.e. treating the ISA as a kind of extended 16-bit architecture with questionable design choices.So ... is it possible to code for an RV32EC microcontroller that actually does real work using only these "C" instructions? And if so, could one generate VHDL implementing a hypothetical RV32C (C instruction only ... no RV32E or RV32I) core?

author: ataradov
author_rank: Super Contributor
timestamp: January 02, 2021, 02:12:44 am
content: Compressed ISA does not have byte- and half-word load/stores. That would be an issue for compilers. Your load/stores would not be very efficient.But yes, I guess it is possible. It would break compatibility with existing compilers, at which point you might as well just make a better ISA with 16-bit instructions.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 02, 2021, 02:38:30 am
content: Adding to this, the instruction set would be pretty limited (limited access to registers, long immediates taking up many instructions to form...), so the end result would not be efficient at all, and not just for load/stores.Note that it wouldn't make it a 16-bit architecture at all. 16-bit instructions != 16-bit CPU. (And for N-bit CPU, see another recent thread.)That could be a choice to drastically reduce code size - at the expense of performance, but really, that would make it very crippled.Also, I don't know the C extension completely (haven't implemented it yet), but I think there's a number of things you could not do, like accessing CSRs (to be confirmed though.)As ataradov said, this doesn't look like a good idea. The RISC-V ISA was not designed for 16-bit instructions. They were added (as an extension) to potentially reduce code size, but you'd miss a number of features and wouldn't have access to many registers at all.Now if I'm not mistaken, if you think of the ARM ISA, there is a Thumb and Thumb-2 mode. The Thumb mode, if I'm not mistaken, only consists of 16-bit instructions, and the Thumb-2 is a mixed 16-/32-bit instruction set, much like the RISC-V base + C extension. So what you are thinking of would essentially be similar to the ARM Thumb instruction set. I don't think this mode is much used (if at all) these days, even with small Cortex M0 cores with limited code memory, but anyway, that would be similar to that IMO.Don't expect this to be proposed, let alone ratified in the RISC-V spec any time soon IMHO, and thus, as ataradov said, don't expect any compiler support for this unless you're ready to do it yourself.

author: I wanted a rude username
author_rank: Frequent Contributor
timestamp: January 02, 2021, 02:54:00 am
content: It'scertainlya bad idea ... so bad I couldn't countenance keeping it to myself.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 02, 2021, 03:31:54 am
content: Quote from: I wanted a rude username on January 02, 2021, 01:59:00 amI can tell you that having a second helping of xmas pudding and I'm anyone's!

author: andersm
author_rank: Super Contributor
timestamp: January 02, 2021, 03:38:41 am
content: The compressed ISA is also lacking eg. the CSR instructions, though I suppose you could design a core that maps everything necessary into memory.

author: I wanted a rude username
author_rank: Frequent Contributor
timestamp: January 02, 2021, 04:55:34 am
content: Remember, it's just a fermented-fruit-fuelled thought experiment.But the proposed core wouldn't have a 32-bit instruction decoder at all ... or implementation of any instructions outside the compressed instruction set ... even useful ones like andersm has mentioned. If you really want to talk about practical side-effects ... wouldn't it requirefewertransistors, and have a shorter critical path?

author: ataradov
author_rank: Super Contributor
timestamp: January 02, 2021, 04:58:31 am
content: It would be simpler, for sure. But if you are doing an incompatible thing, then just make a good incompatible ISA that is even easier to implement.

author: brucehoult
author_rank: Super Contributor
timestamp: January 02, 2021, 06:30:54 am
content: RVC is certainly not designed to be a stand-alone instruction set. There was an alternative proposal called xcondensed floating around four years ago (before ratification) that *was* intended to work for both code compression and as a stand-alone 16 bit opcode ISA:https://groups.google.com/a/groups.riscv.org/g/isa-dev/c/iK3enKGb5bw/m/cuVAq0J8EAAJThe problems with using the current C extension stand alone would include:- C.JAL only has a range of +/ 2 KB, so C.JALR would be needed much more often, and it has no offset, so the entire address would have to be formed in a register. C.J doesn't have any more range than C.JAL.- there is no C.AUIPC, so PIC code further than +/- 2 KB will need a C.JAL to a stub function (maybe just a C.RET) or to the next instruction and then extract the RA and do a series of shifts and adds to form the offset to add to it before the C.JALR.- the only conditional branches are C.BEQZ and C.BNEZ. Branching on inequalities would require a subtract followed by a right shift by 31 (assuming RV32) to keep only the sign bit, then C.BEQZ or C.BNEZ.- there are no Set-register-on-comparison instructions so that will have to be faked with conditional branch code and load immediate.- there are no variable shifts. You'll have to loop on a single-bit shift or pick apart the shift amount into bits and apply shift-by-{1,2,4,8,16} individually with conditional code.- no byte or halfword loads and stores, so you'll need a lot of shifting and masking, especially for byte stores.- only C.LI, C,LUI, C.MV, C.ADD, C.ADDI, C.SLLI, and full register loads and stores to/from the stack can access all 32 registers. Other arithmetic, branches, loads/stores etc can only use registers 8 to 15.- obviously you have no atomic instructions, no CSR instructions, no multiply/divide.So, yes, it's Turing-complete and you *can* use just C, but the work-arounds required will I think use so many extra instructions as to make the code bigger than RV32I, let alone RV32IC.I don't even think you'd save much circuitry, as in decoding it you'll naturally want to get operation + 5 bit rd + 5 bit rs1 + 5 bit rs2 + 32 bit immediate/offset passing up the execution pipeline *anyway*

author: NiHaoMike
author_rank: Super Contributor
timestamp: January 02, 2021, 08:51:50 pm
content: Microcontrollers less than 32 bit are becoming an increasingly shrinking niche as 32 bit microcontrollers are getting cheaper. 16 bit is an even smaller niche since there's not much that can be done well with less than 32 bits but cannot be done well with 8 bits.

author: brucehoult
author_rank: Super Contributor
timestamp: January 02, 2021, 09:31:52 pm
content: Quote from: NiHaoMike on January 02, 2021, 08:51:50 pmThere are two different forces both driving the same thing.In stand-alone microcontroller chips as the cheapest node gets smaller (or simply as bigger nodes stop being made at all) the die size becomes limited by the pads to connect to the pins (and I guess by the big transistors driving them?) and you don't save any money by having a tiny 8 bit CPU sitting in the middle of a lot of unused silicon.As one corner of a bigger chip, you *can* save space with an 8 bit CPU, but often you need to address resources in the larger chip's 32 bit or 64 bit address space if you don't want the application processor to be distracted by copying data back and forth to your buffers.

author: I wanted a rude username
author_rank: Frequent Contributor
timestamp: January 02, 2021, 10:46:53 pm
content: Thanks for the detailed explanation, Bruce. Seems I snorted too much pink icing sugar.NiHaoMike: "And for my next suggestion, a pure 24-bit ISA!"

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 03, 2021, 05:19:07 pm
content: Well, I'm not sure exactly what you are thinking of, but again the register width itself has little to do with the width of the instructions. What mainly affects instruction width is the number of registers to address, the number of sources, and the overall number of instructions to code, of course. Not the register width. Those are two different things, only linked in some way for a pretty limited number of cases/instructions.So I guess you could design a 24-bit core from the RISC-V ISA with relatively minor modifications to the instruction set. I haven't given it a lot of thought, but at first sight, that doesn't seem overly complicated. And again, a "24-bit" CPU doesn't need to have 24-bit intructions. This is not related.Now back to instruction width, there are a number of ways to achieve code size reduction, including the mixed-width approach such as with ARM Thumb(s), RISC-V C, and others. There are other approaches too, such as variable-length instructions with a smaller granularity, which are more complicated to implement and take up more resources. You could also imagine some kind of on-the-fly code decompression, although I'm not sure how effective that would be vs. complexity, and I don't know if that has been actually done, maybe so.And of course as was said by several people above, that also depends on your goal(s). What are they? Code size reduction? Smaller core (in term of area)? Max frequency? Knowing that apart from silicon area (/FPGA resources), the other factors are all linked, and if, by reducing instruction width, you need more instructions to do the same, you may not gain anything in terms of actual code size or execution speed. The trade-offs have to be carefully evaluated.But that said, I don't think it's stupid per se to design a 16-bit CPU or even 24-bit CPU for particular applications. It would certainly reduce silicon area. But as I said, instruction width is a different matter.

author: bson
author_rank: Unknown
timestamp: January 03, 2021, 08:18:08 pm
content: Quote from: ataradov on January 02, 2021, 04:58:31 amThis would get my vote. Or something of curiosity value, such as a 64-bit version of the pdp-10. (With 32-bit addressing, and 32-bit halfwords.)http://pdp10.nocrew.org/docs/instruction-set/pdp-10.html

author: bson
author_rank: Unknown
timestamp: January 03, 2021, 08:54:05 pm
content: Why not just a version of RISC-V that is universally 16-bit - registers, addressing, etc. Simply drop all 32-bit support. It can have a more compact instruction format, and should be easily approachable:- Start with the regular RV in an FPGA and trim it down- Update the test suite- Add it to the gcc backend (just fork the regular RV backend)- Add gas support (again, fork the RV support code)- Add any support needed for gnu ld/libbfd to do footprint and sectional optimizations and not accidentally link in 32-bit codeThis would actually be quite useful, as it would permit compilingexactlythe same C++ code for either a 16 or 32-bit target if you maintain things like hw trap, sw trap, and interrupt compatibility. With a little thought the assembly to e.g. save processor state during context switches (sw trap) could use a common syntax that works on both. If you have a single scheduler that will work for either you automatically get all the layered facilities - mutexes, condition variables, interrupt threads (just an interrupt stack with a hi-prio RT/FIFO thread associated with it, not as special as it sounds), alignment primitives, and even core and base peripherals.

author: brucehoult
author_rank: Super Contributor
timestamp: January 03, 2021, 09:43:03 pm
content: Quote from: SiliconWizard on January 03, 2021, 05:19:07 pmThe first IBM 801 (the very first deliberately RISC computer) was architected as a 32 bit machine but they only implemented 24 bits in the registers and ALU as 16 MB of address space was enough for experimental purposes. The second version was full 32 bit.Both had 16 and 32 bit instruction sizes. As did the Berkeley RISC-I. Doing this comes right from the origins of RISC, it's not a recent hack going against RISC principles or something like that. The CDC 6600 in 1964 was also very similar to modern RISC and had 15 and 30 bit opcodes (and 60 bit registers).

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 05, 2021, 09:28:34 pm
content: Of course. We now use 32- and 64-bit because both have proven to be useful for a large range of applications and they are industry-standard, but nothing prevents you from using a different width. Oh, and there is one factor that is also a plus, and makes other widths less convenient: that the width be a power-of-two number of bytes. 16, 32, 64, 128 bits comply with this condition; 24 bits, not.As to instruction width, again that's a completely different story.Take the typical RISC approach with instructions coding 2 sources, 1 destination, and the operation to be done. Addressing 32 registers requires 15 bits. With 16-bit instructions ONLY, you'd only have 1 bit left to encode the rest. For 16 registers, 4 bits left, and 8 registers, 7 bits left. That's quite limited.

author: andersm
author_rank: Super Contributor
timestamp: January 05, 2021, 11:47:55 pm
content: SuperH used 16-bit instructions, and 16 registers. One way they solved this was having many instructions implicitly use R0 as one operand.One feature which may become more common is 48-bit instructions, allowing up to 32-bit immediates. Historically, they were found in ColdFire, and was one of the new features in nanoMIPS. I think I've seen a proposal for a RISC-V extension as well.

author: brucehoult
author_rank: Super Contributor
timestamp: January 05, 2021, 11:50:12 pm
content: You haven't said *why* it's convenient (I'd use stronger words than that!) to have the register width be a power of two.There is a trivial reason: if you have for example shift instructions (or similar) with an immediate shift amount in the instructions then on a binary 24 bit machine you will need a 5 bit field for the shift count, but you don't use all of the values. You could potentially put some other instruction in that encoding space. Or using arithmetic encoding of your instructions (ugh).The more serious reason, I think, is easily illustrated by the simple case of accessing arrays. There are two cases to consider:1) an array of registers-sized integers on a byte-addressed machine. To access a you need to multiply n by three. This is relatively simple (shift and add, which can even be 1 instruction, or an addressing mode). The problem is that 2/3 of the array items will require unaligned access, and may cross cache line or VM/TLB page boundaries. It's doable but it's not pretty, and imposes a pretty large burden on small microcontroller implementations.x86 of course supports unaligned access. ARM didn't before ARMv7 and even then I think implementations are allowed to trap and emulate it. The RISC-V spec requires privileged code to execute only aligned accesses. Unaligned accesses must be supported in User mode programs, but they are allowed to be slow. I don't know of any current RISC-V core that supports unaligned accesses in hardware -- they are trapped and emulated by pk, bbl, OpenSBI or whatever Machine mode layer you are using.2) on a word-addressed machine you can make things simple for arrays of words. The problems come when you want to have arrays of bytes or bits. To find which 24 bit word string is in you have to calculate n/3. And then to extract the correct byte from the register you have to calculate n%3 (and probably multiply this by 8 for a shift amount). To find elements in an array of bits you have to calculate n/24 and n%24. These are both far more difficult than the simple shifting and masking in a machine with power-of-two words.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 06, 2021, 12:02:47 am
content: Quote from: andersm on January 05, 2021, 11:47:55 pmYes of course. A number of ways to make the most out of a limited instruction width. Drawback is that the instruction set is less "orthogonal".Quote from: andersm on January 05, 2021, 11:47:55 pmYep. Well, the basis of the proposal is in the RISC-V spec doc. "Expanded Instruction-Length Encoding". There is provision for lengths up to 192 bits (and even more...)Only the 16- and 32-bit lengths are frozen at this point, but it's likely the proposed encoding will be what is decribed there. What isn't spec'ed yet are the actual instructions for 48-bit and over.

author: brucehoult
author_rank: Super Contributor
timestamp: January 06, 2021, 12:16:20 am
content: Quote from: andersm on January 05, 2021, 11:47:55 pmAlso IBM 360 and successors, which use 2 bits in the opcode to indicate 16, 32, or 48 bit instructions.nanoMIPS looks really well designed to me and is a huge (and totally incompatible) improvement over earlier MIPS ISAs. Sadly, the company was dying at the time. Only one chip has been made, and it is exclusively for Mediatek. Patches to gcc for nanoMIPS were developed, and posted to the gcc mailing list, but the entire compiler team was laid off about a day later and no one had cared enough about it to get those patches shepherded through to being merged.RISC-V was designed from the start to support 48 bit and longer instructions, with an encoding scheme published in the reference manual that allows up to I think something like 192 bit instructions, in multiples of 16 bits. There is some debate (partly instigated by me ) about changing to a slightly different encoding scheme, and that was not part of what was ratified in the ISA. There are currently no standard RISC-V instructions using 48 bit or longer opcodes.The Vector extension working group had plans to specify 64 bit opcodes later on, with the current 32 bit opcodes being seen as a compressed ans restricted version of the "full" future 64 bit opcodes. For example, the 32 bit encoding has forced compromises such as using a vsetvli instruction to set the vector length and operand width to in the VTYPE CSR potentially before every vector instruction. In fact that's how gcc initially generates vector instructions. A later pass removes vsetvli instructions that are provably the same as the previous one (e.g. within a basic block). Also, the current vector floating-multiply-add instructions force the destination register to be the same as one of the source operands and come in two flavoursdst = dst + src1 * src2anddst = dst * src1 + src2. A 64 bit encoding will allow both of those to be generalized in a single instruction as well as other things such as expanding predication (currently only v0 can be used as a predicate) and maybe also increasing the number of vector registers.The Code Size Working Group is also considering 48 bit opcodes. A 32 bit load is indeed the main one. *Possibly* also some 32 bit immediate instructions (addi, andi, ori, xori). But those use up a lot of encoding space, and only ever save 1 instruction compared to doing a load-immediate and then a register-to-register operation.Huawei is already using in production some custom instructions, including some 48 bit opcodes such as 32 bit load immediate, and also some things such as push/pop multiple. Someone from Huawei is chairing the Code Size Working Group. We will end up with something incompatible with what Huawei are doing now -- not least because Huawei was rather greedy with their use of opcodes and we need to keep some available for other extensions! They are fine with that (and with orphaning their existing chips) and the difference in code size reduction on their codebase is very very minimal -- they just overdid it initially because they didn't have data yet. my concern with the published long opcode support is that 48 and 64 bit opcodes get nice encodings but then there is a change in encoding technique starting with 80 bits and up that suddenly uses a lot more bits to encode the instruction length. The result is that there really isn't enough room in an 80 bit instruction for a 64 bit literal -- an integer load 64 bit immediate instruction and a FP load double immediate instruction would use ALL the 80 bit opcodes (if they could load to any of the 32 registers).If interested see:https://github.com/riscv/riscv-isa-manual/issues/280

author: I wanted a rude username
author_rank: Frequent Contributor
timestamp: January 06, 2021, 12:43:49 am
content: Quote from: brucehoult on January 05, 2021, 11:50:12 pmUnalignedfloataccess is still unsupported and unhandled on the ARMv8-A Cortex-A53.I favour performance over supporting unaligned operations ... though that's probably the Alpha AXP bias showing, same with big-endian. Alignment frustration is one of the several reasons why 24-bit is a go-to hypothetical architecture for liminal exploration.Quote from: brucehoult on January 06, 2021, 12:16:20 amBBCode interprets  as the start of italic text. You can escape square brackets like [[size=10pt]i] (admittedly annoying).

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 06, 2021, 12:47:51 am
content: Quote from: brucehoult on January 06, 2021, 12:16:20 amYeah. I happen to have downloaded their compiler (GCC for nanoMIPS) back when it was maintained at MIPS. It was based on GCC 6.3.0, and I think this was the latest version they released. No source code of course though, just the binaries.I also have the nanoMIPS32 reference manuals.

author: andersm
author_rank: Super Contributor
timestamp: January 06, 2021, 02:41:57 am
content: Quote from: brucehoult on January 06, 2021, 12:16:20 amThere were someposts to the GCC mailing list last monthfrom Wave Computing indicating they've formed a compiler team, and that one of the things they're working on is refreshing and eventually merging the nanoMIPS support.QuoteI believe that was the one I saw.Quote from: SiliconWizard on January 06, 2021, 12:47:51 amThe current architecture manuals are available atWaveComp's website.

author: brucehoult
author_rank: Super Contributor
timestamp: January 06, 2021, 02:42:19 am
content: Quote from: I wanted a rude username on January 06, 2021, 12:43:49 amOh that's what happened. And I hit it on two successive posts with two different usages (array access and "optional"). Very annoying on a technical board.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 06, 2021, 03:23:01 pm
content: Quote from: andersm on January 06, 2021, 02:41:57 amThanks for the info. I checked - those are exactly the same revision as the ones I alread had.The toolchain happens to still be available on the MIPS website:http://codescape.mips.com/components/toolchain/nanomips/2018.04-02/downloads.html

author: brucehoult
author_rank: Super Contributor
timestamp: January 06, 2021, 09:25:56 pm
content: Quote from: SiliconWizard on January 06, 2021, 03:23:01 pmOhh, that may be worth grabbing for posterity and a play.Seems there's source code there too, for binutils, gcc, gdb, qemu ...

author: bson
author_rank: Unknown
timestamp: January 07, 2021, 01:13:41 am
content: I'm all for supporting unaligned access. The silicon overhead isn't a big deal, while it simplifies both software as well as other hardware peripherals. For example, a TCP segment will consists of a 14-byte ethernet frame header, an IP header, and a TCP header. Without the DMA or ethernet controller extending (padding) the frame header to 16 bytes the IP and TCP headers will be misaligned for 32-bit access. The driver then needs to expect this, and be able to fix it up since e.g. Linux SKBs are supposed to look a certain way (14 bytes frame header followed by the IP header). The opposite when allocating buffers on the transmit side. When dealing with binary byte protocols it's often very convenient to declare a C struct for it, packed, and use that. Relying on byte-by-byte access gets very painful very quickly (with any performance gains of the alignment restrictions quickly lost), so much so that the standard approach is to simply copy stuff around with memcpy to guarantee alignment and then use that. Those copies quickly blow away any performance gains as well. I worked with the early Linux BSP of a Broadcom MIPS based SoC where the ethernet controller couldn't do anything other than 14-byte frame headers and could only perform 256-byte aligned transfers. The Linux IP stack doesn't wrap all its implementation's header accesses with macros, and even 1.5MB/s would peg the kernel's alignment trap emulator. Quickly hacking the ethernet driver to move everything 2 bytes to realign it and opposite on transmit, with a 16-byte faux frame header, made it trivially saturate the onboard 100M ethernet. By comparison, a little silicon to do arbitrary byte aligned accesses against wide cache lines, or even multiple cache lines that may need filling, is trivial. (They'll get filled and reused over and over and over anyway no matter what when in e.g. a protocol stack.)

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 07, 2021, 02:56:33 am
content: Quote from: brucehoult on January 06, 2021, 02:42:19 amI like to use[myself, to post-escape any[or<characters that would otherwise be taken as markup. It generates[<em></em>in the HTML code, which is invisible in the browser.Quote from: bson on January 07, 2021, 01:13:41 amI fully agree wrt. data – I mean, I don't think there is any need to allow arbitrary alignment for instructions, but it is very nice to have for 16-bit and 32-bit data accesses.As a programmer, I'd happily accept even a clock cycle penalty for unaligned 16-bit and 32-bit accesses.Quote from: bson on January 07, 2021, 01:13:41 amFor non-local protocols, and for protocols with a fixed byte order, I disagree. With those, I always recommend C developers use inline endian-specific accessors, likeCode:static inline uint16_t get_u16be(const unsigned char *const u) { return ((uint16_t)(u) << 8) | u; }static inline uint16_t get_u16le(const unsigned char *const u) { return u | ((uint16_t)(u) << 8); }static inline uint32_t get_u32be(const unsigned char *const u) { return ((uint32_t)(u) << 24) | ((uint32_t)(u) << 16) | ((uint32_t)(u) << 24) | u; }static inline uint32_t get_u32le(const unsigned char *const u) { return u | | ((uint32_t)(u) << 8) | ((uint32_t)(u) << 16) | ((uint32_t)(u) << 24); }static inline int16_t get_s16be(const unsigned char *const u) { return ((uint16_t)(u) << 8) | u; }static inline int16_t get_s16le(const unsigned char *const u) { return u | ((uint16_t)(u) << 8); }static inline int32_t get_s32be(const unsigned char *const u) { return ((uint32_t)(u) << 24) | ((uint32_t)(u) << 16) | ((uint32_t)(u) << 24) | u; }static inline int32_t get_s32le(const unsigned char *const u) { return u | | ((uint32_t)(u) << 8) | ((uint32_t)(u) << 16) | ((uint32_t)(u) << 24); }static inline void set_u16le(const unsigned char *const u, const uint16_t v) { u = v; u = v >> 8; }static inline void set_u16be(const unsigned char *const u, const uint16_t v) { u = v >> 8; u = v; }static inline void set_u32le(const unsigned char *const u, const uint32_t v) { u = v; u = v >> 8; u = v >> 16; u = v >> 24; }static inline void set_u32be(const unsinged char *const u, const uint32_t v) { u = v >> 24; u = v >> 16; u = v >> 8; u = v; }static inline void set_u16le(const unsigned char *const u, const int16_t v) { const uint16_t w = v; u = w; u = w >> 8; }static inline void set_u16be(const unsigned char *const u, const int16_t v) { const uint16_t w = v; u = w >> 8; u = w; }static inline void set_u32le(const unsigned char *const u, const int32_t v) { const uint32_t w = v; u = w; u = w >> 8; u = w >> 16; u = w >> 24; }static inline void set_u32be(const unsinged char *const u, const int32_t v) { const uint32_t w = v; u = w >> 24; u = w >> 16; u = w >> 8; u = w; }and leave the C compiler to optimize them properly for the architecture at hand.Also note that the pointer these take is explicitly anunsigned char *instead ofvoid *, because that way you can write your protocol accessors as e.g.Code:static inline uint16_t ipv4_get_length(const unsigned char *packet) { return get_u16be(packet + 2); }static inline uint16_t ipv4_get_id(const unsigned char *packet) { return get_u16be(packet + 4); }static inline uint16_t ipv4_get_fragment_offset(const unsigned char *packet) { return get_u16be(packet + 6) & 0x1FFF; }static inline uint32_t ipv4_get_source(const unsigned char *packet) { return get_u32be(packet + 8); }static inline uint32_t ipv4_get_destination(const unsigned char *packet) { return get_u32be(packet + 12); }While one would think there is a performance penalty compared to native unaligned address support with a byte swap when needed, current C compilers are learning about this pattern, and some can already replace it with the faster direct unaligned accesses if the underlying hardware supports it. Thus, in my opinion, it is reasonable to leave the optimization to the C compiler.

author: DiTBho
author_rank: Super Contributor
timestamp: January 07, 2021, 04:44:01 am
content: (kidding)There are programs that try to represent a binary as a graphical image, and a couple of hackers-artists on Youtube are using this technique for their video, and it actually seems that when they "hack" something completely unknown they can recognize the unknown CPU by just looking at its "code density".Amazing, even on live performance, I watched hackers guessing which CPU is by just looking at the "density-image" picture.

author: zl2wrw
author_rank: Regular Contributor
timestamp: February 07, 2021, 05:05:45 am
content: This is an interesting paper by Peijie Li (Berkeley) which proposed changes to optimise the RVC extension:https://www2.eecs.berkeley.edu/Pubs/TechRpts/2019/EECS-2019-107.pdfThe paper claims "The new RVC extension is expected to compress 10% more program instructions and fetch 5% fewerinstruction bits than RVC programs.", but it looks like it failed to make the cut, as RVC was ratified without the proposed changes.

author: brucehoult
author_rank: Super Contributor
timestamp: February 07, 2021, 08:04:51 am
content: That's dated just two weeks before the base ISA was ratified, which is long after the freeze date.

author: brucehoult
author_rank: Super Contributor
timestamp: February 07, 2021, 09:52:10 am
content: I took a little bit ore of a longer look (but not all that thorough) and I find a lot of things there rather dubious.First, they compiled for -march=rv32imac and then found that there aren't any floating point instructions, and therefore decided to delete the compressed floating point instructions use those opcodes for something else.I do tend towards thinking that floating point was over-emphasized in the design of RVC by giving SPECFP benchmarks equal weighting with SPECINT benchmarks. In the real world even FP-heavy programs are either very small (the important code fits into L1 cache easily anyway) or else the FP algorithms make up a small part of the program by code size.And it might well be that microcontrollers that only implement RV32IMAC should be using a different version of RVC that does repurpose the FP opcodes.But this paper doesn't seem like an honest analysis.I find the use of -O3 for all the programs highly dubious. Using -O3 is pretty much explicitly saying to the compiler "I don't care about code size AT ALL". Most people are using -O2 or -Os.I also find some of the concrete results rather dubious.For example the author found that a large proportion of "addi" instructions that didn't fit in an RVC encoding were "addi a5,a5,n" where n was bigger than the 6 bits in the existing RVC encoding, and proposes a new addi instruction that works *only* on register a5 as both src and dst with an 11 bit immediate.I'm shocked.I've looked at a lot of compiled code and this doesn't match my experience. I can't even imagine how such a situation could consistently come about.Assuming it's not a straight out error, the next best guess is that it's all from a single benchmark program.It's also probably very unstable with respect to compiler version and things such as register allocation algorithm. You could add this instruction to RVC and then find that the next compiler version used a4 instead of a5 -- or uses many different registers.There are features in RVC that make assumptions about register usage that the base ISA does not make. For example most RVC instructions work only with registers X8-X15, which just happen to be used as s0-s1 and a0-a5 in the standard ABI. Other RVC instructions assume that x1 is being used as the Link Register and that x2 is being used as the Stack Pointer. All of these are not part of the base RISC-V ISA but are deliberate choices made in the ABI and which are not going to be changed if you want any kind of software compatibility. Register a5 (x15) has no such special place in the ABI or anywhere else.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 07, 2021, 11:57:09 pm
content: Quote from: brucehoult on February 07, 2021, 09:52:10 amTrue. Funnily enough, game graphics and simulated physics tends to need more FP-wielding code than signal analysis and MD simulators: in the latter, the FP stuff tends to be localized to a few library-like routines, whereas the former has them scattered all over (e.g. NPC control logic, physics vector stuff, collision detection, etc.).One exception is JavaScript, which uses IEEE-754 Binary64 (C "double") for all numbers.Quote from: brucehoult on February 07, 2021, 09:52:10 amI'd say the author is detached from the real world, rather than dishonest..intellectually dishonest, yes, but so is a large fraction of our current peer-reviewed literature.This kind of myopic analysis is sadly way too common: picking a very small sample, microbenchmarking it, and then drawing wide conclusions from the results.Correct/reliable benchmarking or analysis takes a LOT of effort. It is a lot of very boring drudge work, and a bit of statistics on top. There is nothing glamorous, challenging, or interesting in doing the work itself. Having done some, I know exactly how tempting it is to just pick a smaller sample, and believe it is sufficient!Quote from: brucehoult on February 07, 2021, 09:52:10 amIn a technical report titled "Reduce code size ...", no less!If one considers Linux distributions as a reliable sample set (which I think in this case is one, at least for code size), then -O2 for the kernel and most of userspace applications, and -Os for embedded userspace applications (routers, busybox, etc.; see e.g. OpenWRT) would have been appropriate.

author: andersm
author_rank: Super Contributor
timestamp: February 08, 2021, 08:04:29 am
content: Quote from: brucehoult on February 07, 2021, 09:52:10 amThe paper claims they're using "-O3 -Os", which a) is nonsense, and b) means -Os, since the last option wins.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 08, 2021, 03:41:28 pm
content: A couple thoughts about this paper:* It's a report from a master's student. I wouldn't call it a "peer-reviewed article". There is even no mention of anyone having supervised this work on the report (which is odd, as master's reports at Berkeley are usually supervised). So we don't really know whoever reviewed this.* Yes the compiler options are odd. Yes "-O3" wouldn't make sense for code size evaluation, but as andersm noted, they also added "-Os", which then wins. So, the result is "-Os", which is OK (but this shows poor command of GCC). The "-msave-restore" flag they added, though, IS definitely dubious here. From what I got (see:https://gcc.gnu.org/onlinedocs/gcc/RISC-V-Options.html#RISC-V-Options), this option WILL inflate code size. But it may make lighter use of registers? The overall result remains to be seen though as to the goal of this report.* As brucehoult noted, they also compiled with "-march=rv32imac", which is a non-sense here as they were supposed to evaluate the use of floating point compressed instructions, and those flags wouldn't produce any?* Finally, even if the report is questionable, the conclusion may still have some validity in some contexts: removing compressed FP instructions and replacing them with other instructions. I think this report doesn't clearly show a benefit due to its flaws though.

author: brucehoult
author_rank: Super Contributor
timestamp: February 09, 2021, 01:38:19 am
content: Quote from: SiliconWizard on February 08, 2021, 03:41:28 pmWhat?No, using -msave-restore decreases code size, in any non-trivial program, by converting a series of saves and restores of registers in function prologues and epilogues to a subroutine call (a tail call for the epilogue, so the RET can be dropped).It's the virtual equivalent of adding a load/store multiple instruction to the ISA. It takes a couple of extra clock cycles to get to and from the save/restore subroutine but that's not actually much overhead.Last I looked at it, the complete set of subroutines for the save/restore took 96 bytes of code. So your own code needs to be at least 5 KB maybe to start to win.Typically, -msave-restore makes programs about 3% smaller and 3% slower. Though for some programs -- for example CoreMark on the FE310 -- it actually makes the program faster by fitting the hot code into the L1 instruction cache.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 09, 2021, 04:09:25 pm
content: Quote from: brucehoult on February 09, 2021, 01:38:19 amI've never used this option, but from the description, I was expecting possibly inflated code size. I'm also expecting possible impact on register pressure. But it looks like its effect on both is actually non-trivial to predict, largely depending on the source code itself.Out of curiosity, I compiled Coremark for my RISC-V core with this option enabled, and compared without this option. (RV32IM, so no compressed instructions here. Also, it's using printf() from newlib, thus the resulting size.)Final binary size:* With '-msave-restore': 72116 bytes* Without '-msave-restore': 72780 bytesThat's indeed smaller. Also compared Coremark results.So, with the option:* Code size: -0.91%* Coremark/MHz: -0.37%Of course that's just one example, but it doesn't look worth the trouble to me.

author: brucehoult
author_rank: Super Contributor
timestamp: February 09, 2021, 10:40:16 pm
content: And that is why push/pop multiple was left out of the RISC-V ISA. It saves a lot less code than many people imagine, at the cost of a lot of complication.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 10, 2021, 01:36:57 am
content: Quote from: brucehoult on February 09, 2021, 10:40:16 pmMakes sense indeed.