見出し画像

4ポートRAMを書き直した

背景

前回のNote
仕様の修正とcocotbでちゃんとテストしよう

仕様

  1. 256Byte(256データ)のメモリブロック内蔵。合計8KB

  2. 8bit幅のRead、Writeポートが4個

  3. データbit幅、アドレス幅可変可能

  4. ブロックが競合しない場合は4ポート同時に読み書き可能→対応しない

  5. 競合した場合は上位ポートを優先

コード

quad_port_ram.v

module quad_port_ram #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 8
) (
    input wire clock,
    input wire reset_n,
    input wire [3:0] mem_read_valid,  // 4 read enable signals
    input wire [3:0] we,              // 4 write enable signals
    input wire [ADDR_WIDTH-1:0] raddr[3:0],  // 4 address inputs
    input wire [ADDR_WIDTH-1:0] waddr[3:0],  // 4 address inputs
    input wire [DATA_WIDTH-1:0] data_in[3:0],  // 4 data inputs
    output reg [3:0] mem_read_ready,  // 4 read ready outputs
    output wire [DATA_WIDTH-1:0] data_out0,  // 4 data outputs
    output wire [DATA_WIDTH-1:0] data_out1,  // 4 data outputs
    output wire [DATA_WIDTH-1:0] data_out2,  // 4 data outputs
    output wire [DATA_WIDTH-1:0] data_out3  // 4 data outputs
);

// dumpvars
/*
initial begin
    $dumpfile("waveform.vcd");  // VCDファイルの名前を指定
    $dumpvars(0, quad_port_ram);  // ダンプする階層を指定
end
*/

reg [DATA_WIDTH-1:0] data_out [3:0];

assign data_out0 = data_out[0];
assign data_out1 = data_out[1];
assign data_out2 = data_out[2];
assign data_out3 = data_out[3];

// dump用 wire
wire [ADDR_WIDTH-1:0] raddr0 = raddr[0];
wire [ADDR_WIDTH-1:0] raddr1 = raddr[1];
wire [ADDR_WIDTH-1:0] raddr2 = raddr[2];
wire [ADDR_WIDTH-1:0] raddr3 = raddr[3];
wire [DATA_WIDTH-1:0] data_in0 = data_in[0];
wire [DATA_WIDTH-1:0] data_in1 = data_in[1];
wire [DATA_WIDTH-1:0] data_in2 = data_in[2];
wire [DATA_WIDTH-1:0] data_in3 = data_in[3];
wire [ADDR_WIDTH-1:0] waddr0 = waddr[0];
wire [ADDR_WIDTH-1:0] waddr1 = waddr[1];
wire [ADDR_WIDTH-1:0] waddr2 = waddr[2];
wire [ADDR_WIDTH-1:0] waddr3 = waddr[3];

// メモリ配列の定義
reg [DATA_WIDTH-1:0] ram [(2**ADDR_WIDTH)-1:0];

always @(posedge clock or negedge reset_n) begin
    if (!reset_n) begin
        mem_read_ready <= 4'b0;
        data_out[0] <= 0;  // 読み出し
        data_out[1] <= 0; 
        data_out[2] <= 0; 
        data_out[3] <= 0; 
    end else begin
        // 優先度付きアクセス処理
        if (we[0]) begin
            ram[waddr[0]] <= data_in[0];  // ポート0が最優先
        end else if (we[1]) begin
            ram[waddr[1]] <= data_in[1];  // ポート1が次に優先
        end else if (we[2]) begin
            ram[waddr[2]] <= data_in[2];  // ポート2が次に優先
        end else if (we[3]) begin
            ram[waddr[3]] <= data_in[3];  // ポート3が最も低い優先度
        end

        // 読み出し処理
        if (mem_read_valid[0]) begin
            mem_read_ready[0] <= 1'b1;
            mem_read_ready[1] <= 1'b0;
            mem_read_ready[2] <= 1'b0;
            mem_read_ready[3] <= 1'b0;
            data_out[0] <= ram[raddr[0]];
            data_out[1] <= 0;
            data_out[2] <= 0;
            data_out[3] <= 0;
        end else if (mem_read_valid[1]) begin
            mem_read_ready[0] <= 1'b0;
            mem_read_ready[1] <= 1'b1;
            mem_read_ready[2] <= 1'b0;
            mem_read_ready[3] <= 1'b0;
            data_out[0] <= 0;
            data_out[1] <= ram[raddr[1]];
            data_out[2] <= 0;
            data_out[3] <= 0;
        end else if (mem_read_valid[2]) begin
            mem_read_ready[0] <= 1'b0;
            mem_read_ready[1] <= 1'b0;
            mem_read_ready[2] <= 1'b1;
            mem_read_ready[3] <= 1'b0;
            data_out[0] <= 0;
            data_out[1] <= 0;
            data_out[2] <= ram[raddr[2]];
            data_out[3] <= 0;
        end else if (mem_read_valid[3]) begin
            mem_read_ready[0] <= 1'b0;
            mem_read_ready[1] <= 1'b0;
            mem_read_ready[2] <= 1'b0;
            mem_read_ready[3] <= 1'b1;
            data_out[0] <= 0;
            data_out[1] <= 0;
            data_out[2] <= 0;
            data_out[3] <= ram[raddr[3]];
        end else begin
            mem_read_ready[0] <= 1'b0;
            mem_read_ready[1] <= 1'b0;
            mem_read_ready[2] <= 1'b0;
            mem_read_ready[3] <= 1'b0;
            data_out[0] <= 0;
            data_out[1] <= 0;
            data_out[2] <= 0;
            data_out[3] <= 0;
        end
    end
end

endmodule

mem_test.v

`timescale 1ns/1ns
`default_nettype none
module mem_test #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 8
    ) (
    input wire clock,
    input wire reset_n,
    input wire [3:0] mem_read_valid,  // 4 read enable signals
    input wire [3:0] we,              // 4 write enable signals
    input wire [ADDR_WIDTH-1:0] raddr[3:0],  // 4 address inputs
    input wire [ADDR_WIDTH-1:0] waddr[3:0],  // 4 address inputs
    input wire [DATA_WIDTH-1:0] data_in[3:0],  // 4 data inputs
    output reg [3:0] mem_read_ready,  // 4 read ready outputs
    output reg [DATA_WIDTH-1:0] data_out0,  // 4 data outputs
    output reg [DATA_WIDTH-1:0] data_out1,  // 4 data outputs
    output reg [DATA_WIDTH-1:0] data_out2,  // 4 data outputs
    output reg [DATA_WIDTH-1:0] data_out3   // 4 data outputs
    );

    // dump
    // dumpvars
    initial begin
        $dumpfile("waveform.vcd");  // VCDファイルの名前を指定
        $dumpvars(0, inst_quad_port_ram);  // ダンプする階層を指定
    end

    // instance
    quad_port_ram inst_quad_port_ram(
        .clock(clock),
        .reset_n(reset_n),
        .mem_read_valid(mem_read_valid[3:0]),  // 4 read enable signals
        .we(we[3:0]),              // 4 write enable signals
        .raddr(raddr),  // 4 address inputs
        .waddr(waddr),  // 4 address inputs
        .data_in(data_in),  // 4 data inputs
        .mem_read_ready(mem_read_ready),  // 4 read ready outputs
        .data_out0(data_out0),  // 4 data outputs
        .data_out1(data_out1),  // 4 data outputs
        .data_out2(data_out2),  // 4 data outputs
        .data_out3(data_out3)  // 4 data outputs
    );

endmodule

test_q_port_ram.py

import cocotb
from cocotb.triggers import RisingEdge
from cocotb.clock import Clock
from cocotb.triggers import Timer

@cocotb.test()
async def test_q_port_ram(dut):
    """Test with memory dump at the end."""

    # Create a clock on the dut.clock signal with a period of 10ns (100MHz)
    clock = Clock(dut.clock, 10, units="ns")
    cocotb.start_soon(clock.start())  # Start the clock
    
    # inisital value
    dut.reset_n.value = 0
    dut.raddr[0].value = 0
    dut.raddr[1].value = 0
    dut.raddr[2].value = 0
    dut.raddr[3].value = 0
    dut.data_in[0].value = 0
    dut.data_in[1].value = 0
    dut.data_in[2].value = 0
    dut.data_in[3].value = 0
    dut.waddr[0].value = 0
    dut.waddr[1].value = 0
    dut.waddr[2].value = 0
    dut.waddr[3].value = 0
    dut.we.value = 0b0000
    dut.mem_read_valid.value = 0b0000
    
    # reset
    await RisingEdge(dut.clock)
    dut.reset_n.value = 1

    # Run the simulation for a sufficient number of clock cycles
    for _ in range(5):  # Adjust the number of cycles as needed
        await RisingEdge(dut.clock)   
        
    # Write to memory port0
    await write_memory0_1(dut, 0x04, 0x07)
    await write_memory0_1(dut, 0x05, 0x02)
    
    # Write to memory port1
    await write_memory1(dut, 0x01, 0x02)
    await write_memory1(dut, 0x03, 0xff)
    
    # Write to memory port2
    await write_memory1(dut, 0x05, 0x03)
    await write_memory1(dut, 0x06, 0xfe)
    
    # Write to memory port3
    await write_memory1(dut, 0x0e, 0x11)
    await write_memory1(dut, 0x0f, 0x22)
     
    # Write to memory port0&1
    await write_memory0_1(dut, 0x07, 0x06)
    await write_memory0_1(dut, 0x08, 0x04)
    await write_memory0_1(dut, 0x00, 0x06)
    await write_memory0_1(dut, 0x01, 0x04)
    
    # Read
    await read_memory(dut, 0b0100, 0x0e)
    await read_memory(dut, 0b0010, 0x04)
    await read_memory(dut, 0b1000, 0x0e)
    
    # Sequential Read
    await sequential_read_memory(dut, 0b0100, 0x0e)
    await sequential_read_memory(dut, 0b0010, 0x04)
    await sequential_read_memory(dut, 0b0100, 0x05)
    await sequential_read_memory(dut, 0b0001, 0x00)
    await sequential_read_memory(dut, 0b0100, 0x05)
    await sequential_read_memory(dut, 0b1000, 0x05)
    await sequential_read_memory(dut, 0b1000, 0x00)
    await sequential_read_memory(dut, 0b1000, 0x05)
    print("Check readdata from waveform")
    
    # Wait
    for _ in range(10):  # Adjust the number of cycles as needed
        await RisingEdge(dut.clock)
    
    # Dump memory
    dump_memory(dut)
  

async def write_memory0(dut, addr, data):
    """Writes data to the memory at the given address."""
    dut.we.value = 0b0001
    dut.waddr[0].value = addr
    dut.data_in[0].value = data
    await RisingEdge(dut.clock)  
    dut.we.value = 0b0000
    
async def write_memory1(dut, addr, data):
    """Writes data to the memory at the given address."""
    dut.we.value = 0b0010
    dut.waddr[1].value = addr
    dut.data_in[1].value = data
    await RisingEdge(dut.clock)  
    dut.we.value = 0b0000
    
async def write_memory2(dut, addr, data):
    """Writes data to the memory at the given address."""
    dut.we.value = 0b0100
    dut.waddr[2].value = addr
    dut.data_in[2].value = data
    await RisingEdge(dut.clock)  
    dut.we.value = 0b0000
    
async def write_memory3(dut, addr, data):
    """Writes data to the memory at the given address."""
    dut.we.value = 0b1000
    dut.waddr[1].value = addr
    dut.data_in[1].value = data
    await RisingEdge(dut.clock)  
    dut.we.value = 0b0000
    
async def write_memory0_1(dut, addr, data):
    """Writes data to the memory at the given address."""
    dut.we.value = 0b0011
    dut.waddr[0].value = addr
    dut.data_in[0].value = data
    await RisingEdge(dut.clock)  
    dut.we.value = 0b0000
    
async def read_memory(dut, port, addr):
    """Read data from the memory at the given address."""
    # Set the address and enable read
    portnum = port2num(port)
    dut.raddr[portnum].value = addr
    dut.mem_read_valid.value = port
    await RisingEdge(dut.clock)  
    dut.mem_read_valid.value = 0b0000
    
    # Wait for the mem_read_ready signal to be asserted
    for i in range(10): 
        mem_ready = dut.mem_read_ready.value
        #if int(mem_ready) == int(0b0010):
        if int(mem_ready) == port:
            # Wait for the next clock edge to read the data
            data = portnum2dataout(dut, port)
            value = int(data.value)
            print(f"Raw data read from Memory[{addr:#06x}] = {value}")
            break
        else:
            await RisingEdge(dut.clock)  # Check at each clock cycle
    else:
        print(f"Warning: mem_read_ready did not assert for addr {addr:#06x} within 10 clock cycles")
        
def port2num(port):
    if port == 0b0001:
        return 0
    elif port == 0b0010:
        return 1
    elif port == 0b0100:
        return 2
    elif port == 0b1000:
        return 3
    else:
        return 255
    
def portnum2dataout(dut, port):
    if port == 0b0001:
        return dut.data_out0
    elif port == 0b0010:
        return dut.data_out1
    elif port == 0b0100:
        return dut.data_out2
    elif port == 0b1000:
        return dut.data_out3
    else:
        return dut.data_out0
    
async def sequential_read_memory(dut, port, addr):
    """Read data from the memory at the given address."""
    # Set the address and enable read
    portnum = port2num(port)
    dut.raddr[portnum].value = addr
    dut.mem_read_valid.value = port
    await RisingEdge(dut.clock)  
    dut.mem_read_valid.value = 0b0000
    
def dump_memory(dut, start=0x00000, end=0x00010):
    print("=========dump memory==============")
    """Dump a range of memory contents."""
    for addr in range(start, end):
        data = dut.inst_quad_port_ram.ram[addr]
        try:
            value = int(data.value)
            print(f"Memory[{addr:#06x}] = {value:d}")
        except ValueError:
            print(f"Memory[{addr:#06x}] = Invalid Data")


Makefile

TOPLEVEL_LANG = verilog
VERILOG_SOURCES = $(PWD)/mem_test.v  $(PWD)/quad_port_ram.v
TOPLEVEL = mem_test
COCOTB_TEST_MODULES = test_q_port_ram  # Pythonテストモジュール名
SIM=icarus

include $(shell cocotb-config --makefiles)/Makefile.sim

実行結果

% make
VCD info: dumpfile waveform.vcd opened for output.
Raw data read from Memory[0x000e] = 17
Raw data read from Memory[0x0004] = 7
Raw data read from Memory[0x000e] = 17
Check readdata from waveform
=========dump memory==============
Memory[0x0000] = 6
Memory[0x0001] = 4
Memory[0x0002] = Invalid Data
Memory[0x0003] = 255
Memory[0x0004] = 7
Memory[0x0005] = 3
Memory[0x0006] = 254
Memory[0x0007] = 6
Memory[0x0008] = 4
Memory[0x0009] = Invalid Data
Memory[0x000a] = Invalid Data
Memory[0x000b] = Invalid Data
Memory[0x000c] = Invalid Data
Memory[0x000d] = Invalid Data
Memory[0x000e] = 17
Memory[0x000f] = 34
   410.00ns INFO     cocotb.regression                  test_q_port_ram.test_q_port_ram passed
   410.00ns INFO     cocotb.regression                  *****************************************************************************************
                                                        ** TEST                             STATUS  SIM TIME (ns)  REAL TIME (s)  RATIO (ns/s) **
                                                        *****************************************************************************************
                                                        ** test_q_port_ram.test_q_port_ram   PASS         410.00           0.00     108867.10  **
                                                        *****************************************************************************************
                                                        ** TESTS=1 PASS=1 FAIL=0 SKIP=0                   410.00           0.01      59198.76  **
                                                        *****************************************************************************************
                                                        

所感

1時間の作業

(ふう・・・)

この記事が気に入ったらサポートをしてみませんか?