Forum Discussion

Altera_Forum's avatar
Altera_Forum
Icon for Honored Contributor rankHonored Contributor
10 years ago

Inferred RAM: Pass-through logic generated despite no_rw_check

Dear all,

I'm a hobbyist currently learning VHDL. My learning vehicle is a self-developed RISC-V CPU that that I develop in the Lite edition of Quartus Prime 15.1. The CPU is working fine already, but I'd like to get some rough edges smoothed out and understand what's going on.

My CPU has a unit that contains the CPU's registers. Quartus nicely infers RAM for the register file - however, it generates some pass-through logic for the particular read-during-write behavior it is seeing:

--- Quote Start ---

Warning (276020): Inferred RAM node "cpu_toplevel:cpu_instance|registers:reg_instance|regs_rtl_0" from synchronous design logic. Pass-through logic has been added to match the read-during-write behavior of the original design.

--- Quote End ---

The code:


library IEEE;
use IEEE.std_logic_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
library work;
use work.constants.all;
entity registers is
    Port(
        I_clk: in std_logic;
        I_en: in std_logic;
        I_op: in std_logic_vector(1 downto 0);
        I_selS1: in std_logic_vector(4 downto 0);
        I_selS2: in std_logic_vector(4 downto 0);
        I_selD: in std_logic_vector(4 downto 0);
        I_dataAlu: in std_logic_vector(XLEN-1 downto 0);
        I_dataMem: in std_logic_vector(XLEN-1 downto 0);
        O_dataS1: out std_logic_vector(XLEN-1 downto 0);
        O_dataS2: out std_logic_vector(XLEN-1 downto 0)
    );
end registers;
architecture Behavioral of registers is
    type store_t is array(1 to 31) of std_logic_vector(XLEN-1 downto 0);
    signal regs: store_t := (others => X"00000000");
    attribute ramstyle : string;
    attribute ramstyle of regs : signal is "no_rw_check"; -- why does Quartus still add pass-through logic?
begin
    process(I_clk)
    begin
        if rising_edge(I_clk) and I_en = '1' then
            -- TODO: find out why synthesis sees read-during-write behavior
    
            case I_op is
                when REGOP_READ =>
                    if I_selS1 = R0 then
                        O_dataS1 <= X"00000000";
                    else
                        O_dataS1 <= regs(to_integer(unsigned(I_selS1)));
                    end if;
                    if I_selS2 = R0 then
                        O_dataS2 <= X"00000000";
                    else
                        O_dataS2 <= regs(to_integer(unsigned(I_selS2)));
                    end if;
                
                
                when REGOP_WRITE_ALU =>
                    if I_selD /= R0 then
                        regs(to_integer(unsigned(I_selD))) <= I_dataAlu;
                    end if;
                
                
                when REGOP_WRITE_MEM =>
                    if I_selD /= R0 then
                        regs(to_integer(unsigned(I_selD))) <= I_dataMem;
                    end if;
                
                when others =>
                    null;
            
            end case;
    
        end if;
    end process;
end Behavioral;

(XLEN is a constant that denotes architecture bit width, in this case the value is 32. Register 0 is always zero, thus there are registers 1 to 31.)

The synthesized design works fine in both simulation and on FPGA (Cyclone IV on a DE0 Nano board), but I currently do not understand following issues:

  • Why does synthesis see read-during-write behavior? As far as I can see (and as far as is intended) there will either be two read accesses (when reading register values) or one write access (when storing results from the ALU or from memory) - but not both in the same cycle. I assume that I misunderstand VHDL semantics in this regard.

  • The pass-through logic is generated despite the "no_rw_check" ramstyle attribute. My understanding is that this attribute is supposed to tell synthesis not to synthesize additional logic to implement the design's read-during-write behavior. The attribute itself seems to be attached properly, for instance I can successfully tell synthesis to generate the registers in logic instead of utilizing ram blocks.

I would greatly appreciate hints to improve my understanding of what's going on there.

Best regards,

Maik

14 Replies

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    wonderful.

    you can put if statment outside case statement. look at changes. So find the structure that your await.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    --- Quote Start ---

    Well, I can always just try my luck ;)

    Btw, it turns out that in my case the second process is not needed, instead I can have the two read accesses in the same process:

    
    			-- this is a pattern that Quartus RAM synthesis understands
    			-- as *not* being read-during-write (with no_rw_check attribute)
    			if write_enabled then
    				regs(to_integer(unsigned(I_selD))) <= data;
    			else
    				O_dataS1 <= regs(to_integer(unsigned(I_selS1)));
    				O_dataS2 <= regs(to_integer(unsigned(I_selS2)));
    			end if;
    

    What *is* important: There apparently can be only one assignment to the output signals for RAM contents. If one includes, e.g., something like

    
    if I_selS1 = R0 then
    	O_dataS1 <= X"00000000";
    end if;
    

    somewhere (to ensure that reads on CPU register 0 always returns zero) Quartus will include the pass-through logic, no matter if no_rw_check is there or not. Instead I currently live with address 0 actually being a memory location, initializing it with zero and never allowing anything but zero to be written there.

    --- Quote End ---

    What would make sense, because the registers in the ram blocks have no mux feeding them. If you code it like this then it has to move the register out of the ram and into general logic. So if you infer a mux, then it becomes an async ram and passthrough would be needed.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    This indeed makes perfect sense and also explains why my original code is synthesized in the observed fashion. Seems that the "read-during-write" problem as implied by the synthesizer warning is not about concurrent reads and writes at all in this case ;)

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    Where does your R0?

    If you want to ensure reading always 0 , just create register with initialized value, and use keep attribute or preserve assignment. No operation write to register . or you can separate by AND-gate internal and external.

    you can use one register near RAM to output on sclear or sload signals.

    I don't know but RAM should have async clear for input/output register. will it be good solution?