@always Simulation: Wrong clock edge used in RAM

Hi all,

I am working on the following:

def ram(NUM_ENTRIES, clk, writeaddress, data, write, readaddress, result, READ_DURING_WRITE='Old Data'):
    ''' SingleClockFifo: the RAM '''
    # handle structured types
    if isinstance(data, (Array, StructType)):
        # make a ShadowSignal aggregating everything into a single intbv
        ramdata = data.tointbv()
        ramresult = ramdata.copy()
        # make a structured output object out of the internal intbv
        # i.e. replace the elements by shadowsignals
        result.fromintbv(ramresult)
    else:
        ramdata = data
        ramresult = result

    storage = Array((NUM_ENTRIES,), ramdata)

    if READ_DURING_WRITE == 'New Data':
        readaddressd1 = readaddress.copy()

        @always(clk, readaddressd1)
        def synch():
            '''
                SingleClockFifo: writing and reading FIFO ram
                note: this infers a RAM with 'New Data' Read-During-Write Behaviour
            '''
            if clk.posedge:
                readaddressd1.next = readaddress
                if write:
                    storage[writeaddress].next = ramdata
            # always reading ...
            ramresult.next = storage[readaddressd1]

        return synch

I used an @always specifically to conform to Altera’s guidelines to infer this kind of RAM: https://documentation.altera.com/#/00107487-AA$NT00064114. If I split this into an @always_seq and an @always_comb the simulation works fine.

The testbench:

def tb_ram():
    Clk = Signal(bool(0))
    wa = Signal(intbv()[4:])
    ra = Signal(intbv()[4:])
    wr = Signal(bool(0))
    data = Signal(intbv()[4:])
    result = Signal(intbv()[4:])

    dut = ram(16, Clk, wa, data, wr, ra, result, READ_DURING_WRITE='New Data')

    ClkCount = Signal(intbv(0)[8:])
    tCK = 10

    @instance
    def genclk():
        yield hdlutils.genClk(Clk, tCK, ClkCount)

    @instance
    def stimulusin():
        wa.next = 0
        ra.next = 0
        yield hdlutils.delayclks(Clk, tCK, 2)
        data.next = 0x1
        yield hdlutils.pulsesig(Clk, tCK, wr)
        wa.next += 1
        yield hdlutils.delayclks(Clk, tCK, 10)

        data.next = 0x2
        yield hdlutils.pulsesig(Clk, tCK, wr)
        wa.next += 1
        yield hdlutils.delayclks(Clk, tCK, 10)

        data.next = 0x3
        yield hdlutils.pulsesig(Clk, tCK, wr)
        wa.next += 1
        data.next = 0x4
        yield hdlutils.pulsesig(Clk, tCK, wr)
        wa.next += 1
        yield hdlutils.delayclks(Clk, tCK, 10)

        ra.next += 1
        yield hdlutils.delayclks(Clk, tCK, 10)

        ra.next += 1
        yield hdlutils.delayclks(Clk, tCK, 1)
        ra.next += 1
        yield hdlutils.delayclks(Clk, tCK, 10)

        raise StopSimulation

    return dut, genclk, stimulusin

The generated VHDL code is what I expected:

-- File: ram.vhd
-- Generated by MyHDL 1.0dev
-- BEWARE: peppered with Array, StructType,
--         advanced ShadowSignal functionality, and more
--         by Josy Boelen (josyb)
--
-- Date: Sat Feb 25 16:56:48 2017


library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use std.textio.all;

use work.pck_myhdl_10.all;

entity ram is
	port(
		clk          : in  std_logic;
		writeaddress : in  unsigned(3 downto 0);
		data         : in  unsigned(3 downto 0);
		write        : in  std_logic;
		readaddress  : in  unsigned(3 downto 0);
		result       : out unsigned(3 downto 0)
	);
end entity ram;

-- SingleClockFifo: the RAM 

architecture Behavioural of ram is
	type a16_u4 is array (0 to 16 - 1) of unsigned(3 downto 0);

	signal readaddressd1 : unsigned(3 downto 0);
	signal storage       : a16_u4;

begin

	-- SingleClockFifo: writing and reading FIFO ram
	-- note: this infers a RAM with 'New Data' Read-During-Write Behaviour
	synch : process(all) is
	begin
		if rising_edge(clk) then
			readaddressd1 <= readaddress;
			if bool(write) then
				storage(to_integer(writeaddress)) <= data;
			end if;
		end if;
		result <= storage(to_integer(readaddressd1));
	end process synch;

end architecture Behavioural;

But the simulation triggers on the negative clock edge:

As you can see the storage is updated on the negative clock edge. Although the output port result seems to reflect the expected behaviour.

If I use the negedge nothing gets written, i.o.w. the circuit is dead.

@josyb You will be better off if you use the Verilog template for your MyHDL. The VHDL tempate -> MyHDL doesn’t quite map (IMO).

Is that the waveform viewer you use in Eclipse?

def ram(NUM_ENTRIES, clk, writeaddress, data, write, readaddress, result, READ_DURING_WRITE='Old Data'):
    ''' SingleClockFifo: the RAM '''
    # handle structured types
    if isinstance(data, (Array, StructType)):
        # make a ShadowSignal aggregating everything into a single intbv
        ramdata = data.tointbv()
        ramresult = ramdata.copy()
        # make a structured output object out of the internal intbv
        # i.e. replace the elements by shadowsignals
        result.fromintbv(ramresult)
    else:
        ramdata = data
        ramresult = result

    storage = Array((NUM_ENTRIES,), ramdata)

    if READ_DURING_WRITE == 'New Data':
        readaddressd1 = readaddress.copy()

        @always(clk.posedge)
        def ram_write():
            '''
                SingleClockFifo: writing and reading FIFO ram
                note: this infers a RAM with 'New Data' Read-During-Write Behaviour
            '''
            readaddressd1.next = readaddress
            if write:
                storage[writeaddress].next = ramdata

    @always_comb
    def ram_read():
            # always reading ...
            ramresult.next = storage[readaddressd1]

        return synch

@cfelton I am using that two-process variant, I’ll see how it turns out in Vivado/Quartus later. I just wanted to find out why the simulation goes wrong. I looked through MyHDL’s source code but I don’t seem to grasp how things work there…
Nice waveform viewer, isn’t it? You can find ‘Impulse’ on toem.de