[HN Gopher] A Single-Cycle 64-Bit RISC-V Register File
       ___________________________________________________________________
        
       A Single-Cycle 64-Bit RISC-V Register File
        
       Author : hasheddan
       Score  : 67 points
       Date   : 2023-08-04 13:58 UTC (9 hours ago)
        
 (HTM) web link (danielmangum.com)
 (TXT) w3m dump (danielmangum.com)
        
       | kouteiheika wrote:
       | > All RISC-V architectures use 32 general purpose registers
       | (GPRs)
       | 
       | Strictly speaking this is incorrect. There are variants called
       | rv32e/rv64e which have 16 general purpose registers. These are
       | particularly nice as a VM ISA since the reduced number of
       | registers allows you to JIT the code to AMD64 (which itself only
       | has 16 GPRs) without having to spill the registers.
        
         | hasheddan wrote:
         | Author here -- that is a great call out, thanks for the
         | correction!
        
         | bonzini wrote:
         | In practice you will need some extra state, for example the
         | RISC-V program counter and the x86 stack, so it's not really
         | possible to map registers 1:1. Also some instructions on x86
         | need extra registers, or they can only refer to fixed registers
         | (e.g. EAX and EDX for multiply-high).
        
         | bpye wrote:
         | There are some MCUs that use RV32E also - the CH32V003 [0]
         | comes to mind.
         | 
         | [0] - https://github.com/openwch/ch32v003
        
       | jhallenworld wrote:
       | In many RAMs, if you read and write to the same address on the
       | same edge, the read returns the old RAM contents, not the new
       | one. So you have to bypass the RAM- you add a mux and a register
       | to use the new data instead of the old if the addresses happened
       | to be the same. In FPGAs, this bypass logic is sometimes
       | automatically generated, depending on the coding style you used
       | for inferred RAM.
       | 
       | This is in addition to bypassing the ALU stage.
       | 
       | So the computer architecture question is whether or not massive
       | amounts of bypassing (with resulting slower fMAX) is worth it,
       | vs. stalling. You need a representative test suite to answer
       | this.
        
         | IshKebab wrote:
         | This isn't RAM, and you want to read the old value anyway.
        
       | codedokode wrote:
       | Sadly the article explains nothing. For example, it doesn't
       | explain how the Verilog (or VHDL) code will be implemented at
       | gate level, is it expensive or not (in terms of gate count), how
       | long the critical path will be, can we pipeline it, how to
       | implement several writing ports etc.
       | 
       | I think that one shouldn't write Verilog/VHDL code unless one
       | clearly understands how it will be transformed into gates.
        
         | okl wrote:
         | It's Verilog in this article.
        
         | boesboes wrote:
         | Maybe you are not the target audience? I found it a decently
         | informative read myself.
         | 
         | Why is it necessary to understand how verilog is exactly
         | transformed into gates? Sounds like gatekeeping to me, but
         | please enlighten me. I have in the past written hdl and I
         | didn't even have a FPGA
        
           | codedokode wrote:
           | Because Verilog is not C where typically a line of code turns
           | into several machine code commands. And even if you write
           | inefficient code, CPUs are so fast that you won't notice it.
           | In Verilog a simple "+" or "*" can turn into thousands of
           | gates. Without understanding this your designs will be
           | inefficient and slow. Unlike CPU cycles, silicon and
           | transistors are not free.
           | 
           | Also for a design to be fast you need to use pipelining and
           | to slice your combinational logic into thin layers. Again,
           | you won't be able to do it without understanding how your
           | code translates to gates.
        
             | RetroTechie wrote:
             | > In Verilog a simple "+" or "*" can turn into thousands of
             | gates. Without understanding this your designs will be
             | inefficient and slow.
             | 
             | Trying hard to avoid this _because it is inefficient_ , is
             | (imho) a typical case of premature optimization. Who cares
             | whether you're wasting CPU cycles (in C) or FPGA logic
             | (Verilog & co) when you're just learning what's what?
             | 
             | But there's a better reason: to make sure that whoever is
             | learning Verilog (or other HDL) actually _understands_ what
             | they are trying to do.
             | 
             | With that understanding in place, the learning becomes
             | "what logic construct(s) would be suitable" followed by
             | "what Verilog to write that describes that logic".
             | 
             | Versus: "Verilog intro course has this example, does it
             | compile? And does it appear to do what I think the Verilog
             | says it should do?".
             | 
             | In programmer's parlance: semantics vs. syntax. The
             | algorithm + how it maps to a CPU's resources, vs. red tape
             | required to implement it.
             | 
             | If you 'feel' the syntax but underlying semantics is black
             | magic, then you could keep stumbling in the dark forever.
             | Spitting out copypasta along the way.
             | 
             | But if you understand basic concepts of the underlying hw
             | (and how you intend to use that), learning syntax to
             | describe that is straightforward.
        
               | alain94040 wrote:
               | It goes beyond being inefficient. It's the wrong mental
               | model.
               | 
               | Chip designers intuitively visualize interconnected
               | blocks of logic. They draw rectangles with wires between
               | them.
               | 
               | To learn this intuition, writing spaghetti Verilog code
               | that runs in a simulator is counter-productive. It's a
               | common mistake that people coming from a software
               | background make all the time, and that has been discussed
               | to death. So with some appeal to authority, trust us and
               | learn proper hardware design, hopefully it will be more
               | rewarding and will "click" faster.
        
             | minipci1321 wrote:
             | > And even if you write inefficient code, CPUs are so fast
             | that you won't notice it.
             | 
             | I think you are arguing for the right cause but with wrong
             | arguments. In C language, not understanding in details what
             | exactly the code does, can be just as disastrous as in
             | Verilog. Internet examples abound.
        
           | d_tr wrote:
           | While it is not strictly necessary in all scenarios, I think
           | you will just not be able to get efficient circuits if you do
           | not have a sense of what type of logic your verilog code
           | snippets map to.
           | 
           | I honestly believe that if you do not understand at least
           | some basics, things will be way more painful and opaque than
           | necessary, so it is a no-brainer IMO.
           | 
           | You also do not need to have an FPGA to learn this stuff.
        
         | hasheddan wrote:
         | Author here -- thanks for the feedback! This is a quick post
         | that is focused on how to logically think about the circuit,
         | but I agree that all of the attributes you enumerated are
         | valuable information as well. I plan to continue diving deeper
         | in future posts. I am currently posting every Friday as part of
         | my goal of gaining a deep understanding of chip design[0].
         | Please feel free to continue to provide feedback on future
         | posts as well!
         | 
         | [0]: https://danielmangum.com/posts/a-three-year-bet-on-chip-
         | desi...
        
           | boesboes wrote:
           | I liked the post. as a novice with fpgas and hdls, I find
           | posts like this very approachable. Refreshing tbh
        
           | therealcamino wrote:
           | I think the article is fine but what you're really driving
           | towards is the idea of synchronous design, and how that
           | simplifies design and analysis. Some of the objections here
           | might go away if it were framed that way.
        
             | hasheddan wrote:
             | That's a great point. Thanks for the feedback!
        
         | monocasa wrote:
         | You don't normally write register files in an HDL anyway (at
         | least in the hard and soft flows I've dealt with). You don't
         | want to build them out of regular logic so you end up wrapping
         | some hard IP blocks. Block RAM on FPGAs, or the output of
         | register file hard block generators on ASIC flows. The HDL just
         | ends up being an interface boundary that gets swapped out for
         | simulation.
        
           | bpye wrote:
           | I think in an FPGA flow you could rely on inferring a block
           | RAM no?
        
             | gsmecher wrote:
             | On FPGAs, a register file probably fits better into
             | distributed RAM than block RAM.
             | 
             | On Xilinx, for example: a 64-bit register file doesn't map
             | efficiently to Xilinx's RAMB36 primitives. You'd need 2
             | RAMB36 primitives to provide a 64-bit wide memory with 1
             | write port and 2 read ports, each addressed separately.
             | Only 6% (32 of 512) entries in each RAMB36 are ever
             | addressable. It's this inefficient because ports, not
             | memory cells, are the contented resource and BRAMs
             | geometries aren't that elastic.
             | 
             | A 64-bit register file in distributed RAM, conversely, is a
             | something like an array of DPRAM32 primitives (see, for
             | example, UG474). Each register would still be stored
             | multiple times to provide additional ports, but depending
             | on the fabric, there's less (or no) unaddressed storage
             | cells.
             | 
             | The Minimax RISC-V CPU
             | (https://github.com/gsmecher/minimax; advertisement
             | warning: my project) is what you get if you chase efficient
             | mapping of FPGA memory primitives (both register-file and
             | RAM) to a logical conclusion. Whether this is actually
             | worth hyper-optimizing really depends on the application.
             | Usually, it's not.
        
             | d_tr wrote:
             | It can infer lots of stuff yes. But sometimes you have to
             | write that part in a somewhat specific way to get synthesis
             | to infer, or you might want the extra control options that
             | the inputs & outputs of a hard block instance offer, or you
             | might have a more elaborate interconnection between several
             | hard blocks and it ends up being easier to just instantiate
             | these hard blocks and set them up manually.
        
             | publicmail wrote:
             | In something as simple as the code in the article, yes.
             | It's likely that the tool will infer a block RAM as long as
             | there are BRAM resources with the required number of ports,
             | etc.
             | 
             | It gets a little more unreliable when you start accessing
             | it in more complex ways though.
             | 
             | From my understanding (I'm no FPGA expert), the code in the
             | article will infer a BRAM with two read ports and one write
             | port. That may be fine.
             | 
             | I actually battled with this recently on a project. I found
             | that the tool was not inferring a block RAM when I expected
             | it to, so I had to modify the Verilog to gate the reads and
             | writes so that only one could happen at a time. That wasn't
             | an issue in my case though.
             | 
             | My takeaway from the exercise was that it's sort of the
             | equivalent of relying on the optimizer of a compiler to
             | recognize the programming pattern and do the right thing.
             | After talking to one of the FPGA guys I work with, he
             | seemed to feel that it's better to just instantiate a
             | vendor IP BRAM directly. The downside is portability
             | though.
        
             | monocasa wrote:
             | I've found that the synthesizer can infer a lot of the
             | time, but I wouldn't say you can rely on it.
        
         | devit wrote:
         | It seems you can get a diagram of the actual circuit like this:
         | 
         | 1. Go to https://edaplayground.com
         | 
         | 2. Paste the code in the right pane labeled "design.sv"; it's
         | probably a good idea to reduce the number of registers to 2 to
         | make the output more readable
         | 
         | 3. On the left, select "Yosys" in tools and simulators (it
         | seems to be the only one that works without additional
         | configuration or fiddling) and enable "Show diagram after run"
         | 
         | 4. Click "Run" and it should open an image with a graph
         | rendering showing the circuit. If it doesn't work, try to
         | disable adblockers or popup blockers (it opens the output as a
         | pop-up apparently)
         | 
         | You can also presumably run yosys locally.
         | 
         | In this case, it implements every register as a D flip-flop;
         | the rs outputs are implemented with muxes from the flip flops,
         | and the D flip-flop value is set to either the current value of
         | the register or to data depending whether write_ctrl is set and
         | rd is equal to the register number.
        
         | alain94040 wrote:
         | Correct. The code shown is only good enough for RTL simulation.
         | It won't help actually generate a chip with multiple read and
         | write ports to a register file.
        
           | therealcamino wrote:
           | That code is synthesizeable and will work fine in an ASIC
           | flow.
        
           | tverbeure wrote:
           | On an FPGA, it will infer a dual-ported BRAM memory just
           | fine. On ASIC, it will generate a flip-flop array, which is
           | good enough for many CPUs.
           | 
           | E.g. in this physical layout of a Swerv RISC-V CPU, the ARF
           | section is the register file, built out of flip-flops:
           | https://tomverbeure.github.io/2019/03/13/SweRV.html#swerv-
           | ph....
        
             | alain94040 wrote:
             | Also, the read ports are asynchronous, which is bad:
             | assign rv1 = registers[rs1];         assign rv2 =
             | registers[rs2];
        
       | sposeray wrote:
       | [dead]
        
       ___________________________________________________________________
       (page generated 2023-08-04 23:01 UTC)