Compare commits
5 Commits
cocotb_pro
...
f64848fb59
| Author | SHA1 | Date | |
|---|---|---|---|
| f64848fb59 | |||
| 3ddf9130d3 | |||
| 8cadd5a02f | |||
| 2c96a87585 | |||
| 3261b66aaa |
@ -12,7 +12,8 @@ module axi4l_reg_map #(
|
||||
axi4l_if.slave s_axil,
|
||||
|
||||
input logic [N_REGS-1:0][31:0] reg_i,
|
||||
output logic [N_REGS-1:0][31:0] reg_o
|
||||
output logic [N_REGS-1:0][31:0] reg_o,
|
||||
output logic [N_REGS-1:0][31:0] reg_pulse
|
||||
);
|
||||
import axi_pkg::*;
|
||||
|
||||
@ -105,6 +106,7 @@ module axi4l_reg_map #(
|
||||
rdata_q <= '0;
|
||||
reg_o <= REG_RST;
|
||||
end else begin
|
||||
reg_pulse <= '0;
|
||||
for (int r = 0; r < N_REGS; r++) begin
|
||||
for (int bit_idx = 0; bit_idx < 32; bit_idx++) begin
|
||||
if (reg_bit_mode_t'(REG_MODE[r][bit_idx]) == REG_BIT_W1S)
|
||||
@ -136,12 +138,33 @@ module axi4l_reg_map #(
|
||||
for (b = 0; b < 32; b = b + 1) begin
|
||||
if (wr_mask[b]) begin
|
||||
unique case (reg_bit_mode_t'(REG_MODE[wr_idx][b]))
|
||||
REG_BIT_RSVD: begin end
|
||||
REG_BIT_RO : begin bresp_q <= 2'b10; end
|
||||
REG_BIT_RW : rw_new[b] = wr_data32[b];
|
||||
REG_BIT_W1S : if (wr_data32[b]) rw_new[b] = 1'b1;
|
||||
REG_BIT_W1C : if (wr_data32[b]) rw_new[b] = 1'b0;
|
||||
default : begin end
|
||||
REG_BIT_RSVD: begin
|
||||
end
|
||||
|
||||
REG_BIT_RO: begin
|
||||
bresp_q <= 2'b10;
|
||||
end
|
||||
|
||||
REG_BIT_RW: begin
|
||||
rw_new[b] = wr_data32[b];
|
||||
end
|
||||
|
||||
REG_BIT_W1S: begin
|
||||
if (wr_data32[b]) begin
|
||||
rw_new[b] = 1'b1;
|
||||
reg_pulse[wr_idx][b] <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
REG_BIT_W1C: begin
|
||||
if (wr_data32[b]) begin
|
||||
rw_new[b] = 1'b0;
|
||||
reg_pulse[wr_idx][b] <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
default: begin
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
@ -174,6 +197,7 @@ module axi4l_reg_map #(
|
||||
REG_BIT_W1C : rd_word[b] = reg_o[rd_idx][b];
|
||||
default : rd_word[b] = 1'b0;
|
||||
endcase
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,37 +1,64 @@
|
||||
package dma_axil_reg_map_pkg;
|
||||
|
||||
localparam int unsigned DMA_AXIL_REG_MAP_N_REGS = 4;
|
||||
localparam int unsigned DMA_AXIL_REG_MAP_N_REGS = 6;
|
||||
|
||||
localparam int unsigned DMA_WRITE_DESC_CONTROL_REG = 0;
|
||||
localparam int unsigned DMA_WRITE_DESC_ADDR_REG = 1;
|
||||
localparam int unsigned DMA_WRITE_DESC_LEN_REG = 2;
|
||||
localparam int unsigned DMA_READ_DESC_CONTROL_REG = 3;
|
||||
localparam int unsigned DMA_READ_DESC_ADDR_REG = 4;
|
||||
localparam int unsigned DMA_READ_DESC_LEN_REG = 5;
|
||||
|
||||
localparam logic [2:0] REG_BIT_RSVD = 3'd0;
|
||||
localparam logic [2:0] REG_BIT_RO = 3'd1;
|
||||
localparam logic [2:0] REG_BIT_RW = 3'd2;
|
||||
localparam logic [2:0] REG_BIT_W1S = 3'd3;
|
||||
localparam logic [2:0] REG_BIT_W1C = 3'd4;
|
||||
|
||||
localparam DMA_WRITE_DESC_CONTROL_REG = 0;
|
||||
localparam DMA_WRITE_DESC_ADDR_REG = 1;
|
||||
localparam DMA_WRITE_DESC_LEN_REG = 2;
|
||||
localparam DMA_READ_DESC_CONTROL_REG = 3;
|
||||
localparam DMA_READ_DESC_ADDR_REG = 4;
|
||||
localparam DMA_READ_DESC_LEN_REG = 5;
|
||||
typedef logic [DMA_AXIL_REG_MAP_N_REGS-1:0][31:0][2:0] reg_mode_map_t;
|
||||
|
||||
localparam logic [DMA_AXIL_REG_MAP_N_REGS-1:0][31:0][2:0] DMA_AXIL_REG_MAP_REG_MODE = '{
|
||||
function automatic reg_mode_map_t make_dma_reg_mode();
|
||||
reg_mode_map_t mode;
|
||||
|
||||
'{REG_BIT_RO, REG_BIT_W1S, default: REG_BIT_RSVD},
|
||||
'{32{REG_BIT_RW}, default: REG_BIT_RSVD},
|
||||
'{32{REG_BIT_RW}, default: REG_BIT_RSVD},
|
||||
'{REG_BIT_RO, REG_BIT_W1S, default: REG_BIT_RSVD},
|
||||
'{32{REG_BIT_RW}, default: REG_BIT_RSVD},
|
||||
'{32{REG_BIT_RW}, default: REG_BIT_RSVD}
|
||||
};
|
||||
mode = '0;
|
||||
|
||||
localparam logic [DMA_AXIL_REG_MAP_N_REGS-1:0][31:0] DMA_AXIL_REG_MAP_REG_RST = '{
|
||||
32'h0000_0000,
|
||||
32'h0000_0000,
|
||||
32'h0000_0000,
|
||||
32'h0000_0000,
|
||||
32'h0000_0000,
|
||||
32'h0000_0000
|
||||
};
|
||||
// По умолчанию всё reserved
|
||||
for (int reg_idx = 0; reg_idx < DMA_AXIL_REG_MAP_N_REGS; reg_idx++) begin
|
||||
for (int bit_idx = 0; bit_idx < 32; bit_idx++) begin
|
||||
mode[reg_idx][bit_idx] = REG_BIT_RSVD;
|
||||
end
|
||||
end
|
||||
|
||||
// WRITE CONTROL
|
||||
mode[DMA_WRITE_DESC_CONTROL_REG][0] = REG_BIT_RO;
|
||||
mode[DMA_WRITE_DESC_CONTROL_REG][1] = REG_BIT_W1S;
|
||||
|
||||
// WRITE ADDR
|
||||
for (int bit_idx = 0; bit_idx < 32; bit_idx++) begin
|
||||
mode[DMA_WRITE_DESC_ADDR_REG][bit_idx] = REG_BIT_RW;
|
||||
end
|
||||
|
||||
// WRITE LEN
|
||||
for (int bit_idx = 0; bit_idx < 32; bit_idx++) begin
|
||||
mode[DMA_WRITE_DESC_LEN_REG][bit_idx] = REG_BIT_RW;
|
||||
end
|
||||
|
||||
// READ CONTROL
|
||||
mode[DMA_READ_DESC_CONTROL_REG][0] = REG_BIT_RO;
|
||||
mode[DMA_READ_DESC_CONTROL_REG][1] = REG_BIT_W1S;
|
||||
|
||||
// READ ADDR
|
||||
for (int bit_idx = 0; bit_idx < 32; bit_idx++) begin
|
||||
mode[DMA_READ_DESC_ADDR_REG][bit_idx] = REG_BIT_RW;
|
||||
end
|
||||
|
||||
// READ LEN
|
||||
for (int bit_idx = 0; bit_idx < 32; bit_idx++) begin
|
||||
mode[DMA_READ_DESC_LEN_REG][bit_idx] = REG_BIT_RW;
|
||||
end
|
||||
|
||||
return mode;
|
||||
endfunction
|
||||
|
||||
localparam reg_mode_map_t DMA_AXIL_REG_MAP_REG_MODE = make_dma_reg_mode();
|
||||
|
||||
endpackage
|
||||
@ -2,7 +2,7 @@ module axi_crossbar_wrapper #(
|
||||
parameter int SLAVE_QTY = 3,
|
||||
parameter int MASTER_QTY = 3,
|
||||
parameter int ADDR_WIDTH = 32,
|
||||
parameter int DATA_WIDTH = 32
|
||||
parameter int DATA_WIDTH = 32,
|
||||
parameter int STRB_WIDTH = (DATA_WIDTH/8)
|
||||
)(
|
||||
input wire clk,
|
||||
|
||||
@ -13,7 +13,7 @@ interface axi4_if #(
|
||||
typedef logic [DATA_W/8-1:0] strb_t;
|
||||
typedef logic [ID_W-1:0] id_t;
|
||||
typedef logic [USER_W-1:0] user_t;
|
||||
`AXI4_TYPEDEF_ALL(axi, addr_t, data_t, strb_t, id_t, user_t)
|
||||
`AXI4_TYPEDEF_ALL(axi, addr_t, data_t, strb_t, id_t, user_t);
|
||||
axi_req_t req;
|
||||
axi_resp_t resp;
|
||||
modport master (input aclk, aresetn, output req, input resp);
|
||||
@ -34,7 +34,7 @@ interface axi4l_if #(
|
||||
typedef logic [DATA_W-1:0] data_t;
|
||||
typedef logic [DATA_W/8-1:0] strb_t;
|
||||
typedef logic [USER_W-1:0] user_t;
|
||||
`AXI4L_TYPEDEF_ALL(axil, addr_t, data_t, strb_t, user_t)
|
||||
`AXI4L_TYPEDEF_ALL(axil, addr_t, data_t, strb_t, user_t);
|
||||
axil_req_t req;
|
||||
axil_resp_t resp;
|
||||
modport master (input aclk, aresetn, output req, input resp);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
module axil_cdc_wrapper #(
|
||||
parameter int ADDR_WIDTH = 32
|
||||
parameter int DATA_WIDTH = 32,
|
||||
parameter int ADDR_WIDTH = 32,
|
||||
parameter int DATA_WIDTH = 32
|
||||
)(
|
||||
input wire s_clk,
|
||||
input wire s_rst,
|
||||
@ -83,8 +83,8 @@ module axil_cdc_wrapper #(
|
||||
);
|
||||
|
||||
axil_cdc #(
|
||||
.ADDR_WIDTH (ADDR_WIDTH)
|
||||
.DATA_WIDTH (DATA_WIDTH),
|
||||
.ADDR_WIDTH (ADDR_WIDTH),
|
||||
.DATA_WIDTH (DATA_WIDTH)
|
||||
) i_axil_cdc (
|
||||
.s_clk (s_clk),
|
||||
.s_rst (s_rst),
|
||||
|
||||
@ -92,6 +92,7 @@ module tb_axi4l_reg_map;
|
||||
|
||||
logic [N_REGS-1:0][31:0] reg_i;
|
||||
logic [N_REGS-1:0][31:0] reg_o;
|
||||
logic [N_REGS-1:0][31:0] reg_pulse;
|
||||
|
||||
// debug probes visible from cocotb. they are useful for checking W1S pulse-like behavior
|
||||
logic [31:0] reg0_o;
|
||||
@ -157,7 +158,8 @@ module tb_axi4l_reg_map;
|
||||
.rst_n (rst_n),
|
||||
.s_axil (s_axil_if.slave),
|
||||
.reg_i (reg_i),
|
||||
.reg_o (reg_o)
|
||||
.reg_o (reg_o),
|
||||
.reg_pulse(reg_pulse)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge
|
||||
from cocotb.triggers import ReadOnly, RisingEdge
|
||||
from cocotbext.axi import AxiLiteBus, AxiLiteMaster
|
||||
|
||||
OKAY = 0
|
||||
SLVERR = 2
|
||||
|
||||
REG0_CTRL = 0x00
|
||||
REG0_CTRL = 0x00
|
||||
REG1_STATUS = 0x04
|
||||
REG2_CONFIG = 0x08
|
||||
REG_INVALID = 0x0C # N_REGS=3, so index 3 is bad
|
||||
@ -35,7 +33,8 @@ def _read_data(resp):
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
self.axil = AxiLiteMaster(AxiLiteBus.from_prefix(dut, "s_axil"), dut.clk, dut.rst)
|
||||
self.axil = AxiLiteMaster(AxiLiteBus.from_prefix(
|
||||
dut, "s_axil"), dut.clk, dut.rst)
|
||||
|
||||
async def reset(self):
|
||||
self.dut.rst.value = 1
|
||||
@ -114,28 +113,175 @@ async def test_rw_and_w1c_bits(dut):
|
||||
assert await tb.read32(REG2_CONFIG) == 0xDEADBEEF
|
||||
|
||||
|
||||
def _reg0_pulse_value(dut):
|
||||
"""Return REG0 pulse bits regardless of how the test wrapper exposes them."""
|
||||
if hasattr(dut, "reg0_pulse"):
|
||||
return int(dut.reg0_pulse.value)
|
||||
|
||||
if hasattr(dut, "reg_pulse"):
|
||||
# Packed SystemVerilog array [N_REGS-1:0][31:0]: REG0 occupies bits 31:0.
|
||||
return int(dut.reg_pulse.value) & 0xFFFF_FFFF
|
||||
|
||||
raise AssertionError(
|
||||
"DUT must expose either reg0_pulse[31:0] or packed reg_pulse"
|
||||
)
|
||||
|
||||
|
||||
async def _sample_reg0_pulse_cycles(dut, cycles):
|
||||
"""Sample REG0 pulse output after each active clock edge."""
|
||||
samples = []
|
||||
|
||||
for _ in range(cycles):
|
||||
await RisingEdge(dut.clk)
|
||||
await ReadOnly()
|
||||
samples.append(_reg0_pulse_value(dut))
|
||||
|
||||
return samples
|
||||
|
||||
|
||||
def _assert_single_cycle_bit_pulse(samples, bit, name):
|
||||
"""Check that one selected bit was high for exactly one sampled cycle."""
|
||||
high_cycles = [
|
||||
index for index, value in enumerate(samples)
|
||||
if value & (1 << bit)
|
||||
]
|
||||
assert len(high_cycles) == 1, (
|
||||
f"{name}: expected one high cycle, got {len(high_cycles)}; "
|
||||
f"samples={[f'0x{value:08x}' for value in samples]}"
|
||||
)
|
||||
return high_cycles[0]
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_w1s_seen_on_reg_o_probe(dut):
|
||||
"""Check that a W1S write creates a short-lived internal reg_o bit."""
|
||||
async def test_reg_pulse_is_zero_after_reset(dut):
|
||||
"""No W1 event may be reported after reset without a write."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
seen = False
|
||||
samples = await _sample_reg0_pulse_cycles(dut, 4)
|
||||
assert not any(samples), (
|
||||
"reg_pulse was asserted without a W1 write: "
|
||||
f"{[f'0x{value:08x}' for value in samples]}"
|
||||
)
|
||||
|
||||
async def monitor_w1s_bit():
|
||||
nonlocal seen
|
||||
for _ in range(20):
|
||||
await RisingEdge(dut.clk)
|
||||
if int(dut.reg0_o.value) & 0x1:
|
||||
seen = True
|
||||
|
||||
mon = cocotb.start_soon(monitor_w1s_bit())
|
||||
@cocotb.test()
|
||||
async def test_w1s_generates_single_cycle_reg_pulse(dut):
|
||||
"""Writing 1 to REG0[0] W1S must pulse REG0 pulse bit 0 once."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
monitor = cocotb.start_soon(_sample_reg0_pulse_cycles(dut, 20))
|
||||
await tb.write32(REG0_CTRL, 0x00000001)
|
||||
await mon
|
||||
samples = await monitor
|
||||
|
||||
assert seen, "W1S bit was never observed on reg0_o[0]"
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000004 # W1S reads as 0, W1C reset bit remains set
|
||||
_assert_single_cycle_bit_pulse(samples, bit=0, name="reg_pulse[0][0]")
|
||||
|
||||
# No unrelated W1 bit may pulse.
|
||||
assert not any(value & (1 << 2) for value in samples)
|
||||
|
||||
# W1S is a command bit and is not returned by reads.
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000004
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_w1c_generates_single_cycle_reg_pulse(dut):
|
||||
"""Writing 1 to REG0[2] W1C must pulse bit 2 once and clear state."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000004
|
||||
|
||||
monitor = cocotb.start_soon(_sample_reg0_pulse_cycles(dut, 20))
|
||||
await tb.write32(REG0_CTRL, 0x00000004)
|
||||
samples = await monitor
|
||||
|
||||
_assert_single_cycle_bit_pulse(samples, bit=2, name="reg_pulse[0][2]")
|
||||
|
||||
# No unrelated W1 bit may pulse.
|
||||
assert not any(value & (1 << 0) for value in samples)
|
||||
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000000
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_zero_to_w1_and_rw_write_do_not_generate_reg_pulse(dut):
|
||||
"""Only written ones in W1 fields may create reg_pulse."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
monitor = cocotb.start_soon(_sample_reg0_pulse_cycles(dut, 20))
|
||||
|
||||
# bit1 is RW; both W1 fields receive zero.
|
||||
await tb.write32(REG0_CTRL, 0x00000002)
|
||||
samples = await monitor
|
||||
|
||||
assert not any(samples), (
|
||||
"RW write or zero written to W1 fields generated reg_pulse: "
|
||||
f"{[f'0x{value:08x}' for value in samples]}"
|
||||
)
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000006
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_w1s_and_w1c_pulse_together(dut):
|
||||
"""W1S and W1C ones in one AXI write must pulse on the same cycle."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
monitor = cocotb.start_soon(_sample_reg0_pulse_cycles(dut, 20))
|
||||
await tb.write32(REG0_CTRL, 0x00000005)
|
||||
samples = await monitor
|
||||
|
||||
w1s_cycle = _assert_single_cycle_bit_pulse(
|
||||
samples, bit=0, name="reg_pulse[0][0]"
|
||||
)
|
||||
w1c_cycle = _assert_single_cycle_bit_pulse(
|
||||
samples, bit=2, name="reg_pulse[0][2]"
|
||||
)
|
||||
|
||||
assert w1s_cycle == w1c_cycle, (
|
||||
"W1 bits written by one transaction pulsed on different cycles: "
|
||||
f"W1S={w1s_cycle}, W1C={w1c_cycle}"
|
||||
)
|
||||
|
||||
# Only bits 0 and 2 are allowed to pulse.
|
||||
assert samples[w1s_cycle] == 0x00000005
|
||||
assert await tb.read32(REG0_CTRL) == 0x00000000
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
async def test_each_w1_write_creates_a_new_pulse(dut):
|
||||
"""Two separate W1 writes must create two separate one-cycle pulses."""
|
||||
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
|
||||
tb = TB(dut)
|
||||
await tb.reset()
|
||||
|
||||
monitor = cocotb.start_soon(_sample_reg0_pulse_cycles(dut, 40))
|
||||
|
||||
await tb.write32(REG0_CTRL, 0x00000001)
|
||||
for _ in range(3):
|
||||
await RisingEdge(dut.clk)
|
||||
await tb.write32(REG0_CTRL, 0x00000001)
|
||||
|
||||
samples = await monitor
|
||||
high_cycles = [
|
||||
index for index, value in enumerate(samples)
|
||||
if value & 0x1
|
||||
]
|
||||
|
||||
assert len(high_cycles) == 2, (
|
||||
f"expected two W1S pulses, got cycles {high_cycles}; "
|
||||
f"samples={[f'0x{value:08x}' for value in samples]}"
|
||||
)
|
||||
assert high_cycles[1] > high_cycles[0] + 1, (
|
||||
f"separate writes did not produce separate pulses: {high_cycles}"
|
||||
)
|
||||
|
||||
|
||||
@cocotb.test()
|
||||
|
||||
Reference in New Issue
Block a user