![見出し画像](https://assets.st-note.com/production/uploads/images/153121353/rectangle_large_type_2_f90bad40de53f9551edba3673ccacb98.png?width=1200)
Photo by
brandkojo
4ポートRAMを書き直した。さらに追記
背景
前回のNote
4ポートRAMを設計したがWrite側にもvalidとready信号を追加しよう
コード
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] mem_write_valid, // 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 [3:0] mem_write_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;
mem_write_ready <= 4'b0;
data_out[0] <= 0; // 読み出し
data_out[1] <= 0;
data_out[2] <= 0;
data_out[3] <= 0;
end else begin
// 優先度付きアクセス処理
if (mem_write_valid[0]) begin
mem_write_ready[0] <= 1'b1;
mem_write_ready[1] <= 1'b0;
mem_write_ready[2] <= 1'b0;
mem_write_ready[3] <= 1'b0;
ram[waddr[0]] <= data_in[0]; // ポート0が最優先
end else if (mem_write_valid[1]) begin
mem_write_ready[0] <= 1'b0;
mem_write_ready[1] <= 1'b1;
mem_write_ready[2] <= 1'b0;
mem_write_ready[3] <= 1'b0;
ram[waddr[1]] <= data_in[1]; // ポート1が次に優先
end else if (mem_write_valid[2]) begin
mem_write_ready[0] <= 1'b0;
mem_write_ready[1] <= 1'b0;
mem_write_ready[2] <= 1'b1;
mem_write_ready[3] <= 1'b0;
ram[waddr[2]] <= data_in[2]; // ポート2が次に優先
end else if (mem_write_valid[3]) begin
mem_write_ready[0] <= 1'b0;
mem_write_ready[1] <= 1'b0;
mem_write_ready[2] <= 1'b0;
mem_write_ready[3] <= 1'b1;
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] mem_write_valid, // 4 read 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 [3:0] mem_write_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
.mem_write_valid(mem_write_valid[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
.mem_write_ready(mem_write_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.mem_write_valid.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, 0x02, 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, 0x04, 0x11)
await write_memory1(dut, 0x0f, 0x22)
await write_memory1(dut, 0x0e, 0x12)
await write_memory1(dut, 0x0d, 0x32)
# 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, 0b0001, 0x01)
await read_memory(dut, 0b0010, 0x02)
await read_memory(dut, 0b1000, 0x03)
# Sequential Read
await sequential_read_memory(dut, 0b0001, 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.mem_write_valid.value = 0b0001
dut.waddr[0].value = addr
dut.data_in[0].value = data
await RisingEdge(dut.clock)
dut.mem_write_valid.value = 0b0000
async def write_memory1(dut, addr, data):
"""Writes data to the memory at the given address."""
dut.mem_write_valid.value = 0b0010
dut.waddr[1].value = addr
dut.data_in[1].value = data
await RisingEdge(dut.clock)
dut.mem_write_valid.value = 0b0000
async def write_memory2(dut, addr, data):
"""Writes data to the memory at the given address."""
dut.mem_write_valid.value = 0b0100
dut.waddr[2].value = addr
dut.data_in[2].value = data
await RisingEdge(dut.clock)
dut.mem_write_valid.value = 0b0000
async def write_memory3(dut, addr, data):
"""Writes data to the memory at the given address."""
dut.mem_write_valid.value = 0b1000
dut.waddr[1].value = addr
dut.data_in[1].value = data
await RisingEdge(dut.clock)
dut.mem_write_valid.value = 0b0000
async def write_memory0_1(dut, addr, data):
"""Writes data to the memory at the given address."""
dut.mem_write_valid.value = 0b0011
dut.waddr[0].value = addr
dut.data_in[0].value = data
await RisingEdge(dut.clock)
dut.mem_write_valid.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) == 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")
所感
cocotbが書き方によっては突然エラーになったり、
このライブラリは安定しないようです。
調べたところでは、FPGAメーカーによりますが、今のFPGAのEDAソフトは8ポートメモリだって、16ポートメモリだって、モデル記述するだけで合成してくれるようです。
そりゃ、信号の調停部分は時間さえかければ手書きできる部分ですので、ChatGPTがある時代。自動化されてると。
今は便利ですよね。
(部品発注もやりました・・・)