add wrappers 1.0 #1

Merged
baulin.fa merged 2 commits from dev into master 2026-07-10 17:31:51 +03:00
4 changed files with 651 additions and 26 deletions
Showing only changes of commit 3ddf9130d3 - Show all commits

View File

@ -12,7 +12,8 @@ module axi4l_reg_map #(
axi4l_if.slave s_axil, axi4l_if.slave s_axil,
input logic [N_REGS-1:0][31:0] reg_i, 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::*; import axi_pkg::*;
@ -105,6 +106,7 @@ module axi4l_reg_map #(
rdata_q <= '0; rdata_q <= '0;
reg_o <= REG_RST; reg_o <= REG_RST;
end else begin end else begin
reg_pulse <= '0;
for (int r = 0; r < N_REGS; r++) begin for (int r = 0; r < N_REGS; r++) begin
for (int bit_idx = 0; bit_idx < 32; bit_idx++) 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) 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 for (b = 0; b < 32; b = b + 1) begin
if (wr_mask[b]) begin if (wr_mask[b]) begin
unique case (reg_bit_mode_t'(REG_MODE[wr_idx][b])) unique case (reg_bit_mode_t'(REG_MODE[wr_idx][b]))
REG_BIT_RSVD: begin end REG_BIT_RSVD: begin
REG_BIT_RO : begin bresp_q <= 2'b10; end end
REG_BIT_RW : rw_new[b] = wr_data32[b];
REG_BIT_W1S : if (wr_data32[b]) rw_new[b] = 1'b1; REG_BIT_RO: begin
REG_BIT_W1C : if (wr_data32[b]) rw_new[b] = 1'b0; bresp_q <= 2'b10;
default : begin end 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 endcase
end end
end end
@ -174,6 +197,7 @@ module axi4l_reg_map #(
REG_BIT_W1C : rd_word[b] = reg_o[rd_idx][b]; REG_BIT_W1C : rd_word[b] = reg_o[rd_idx][b];
default : rd_word[b] = 1'b0; default : rd_word[b] = 1'b0;
endcase endcase
end end
end end

View File

@ -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_i;
logic [N_REGS-1:0][31:0] reg_o; 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 // debug probes visible from cocotb. they are useful for checking W1S pulse-like behavior
logic [31:0] reg0_o; logic [31:0] reg0_o;
@ -157,7 +158,8 @@ module tb_axi4l_reg_map;
.rst_n (rst_n), .rst_n (rst_n),
.s_axil (s_axil_if.slave), .s_axil (s_axil_if.slave),
.reg_i (reg_i), .reg_i (reg_i),
.reg_o (reg_o) .reg_o (reg_o),
.reg_pulse(reg_pulse)
); );
endmodule endmodule

View File

@ -1,8 +1,6 @@
# SPDX-License-Identifier: MIT
import cocotb import cocotb
from cocotb.clock import Clock from cocotb.clock import Clock
from cocotb.triggers import RisingEdge from cocotb.triggers import ReadOnly, RisingEdge
from cocotbext.axi import AxiLiteBus, AxiLiteMaster from cocotbext.axi import AxiLiteBus, AxiLiteMaster
OKAY = 0 OKAY = 0
@ -35,7 +33,8 @@ def _read_data(resp):
class TB: class TB:
def __init__(self, dut): def __init__(self, dut):
self.dut = 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): async def reset(self):
self.dut.rst.value = 1 self.dut.rst.value = 1
@ -114,28 +113,175 @@ async def test_rw_and_w1c_bits(dut):
assert await tb.read32(REG2_CONFIG) == 0xDEADBEEF 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() @cocotb.test()
async def test_w1s_seen_on_reg_o_probe(dut): async def test_reg_pulse_is_zero_after_reset(dut):
"""Check that a W1S write creates a short-lived internal reg_o bit.""" """No W1 event may be reported after reset without a write."""
cocotb.start_soon(Clock(dut.clk, 10, units="ns").start()) cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
tb = TB(dut) tb = TB(dut)
await tb.reset() 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 tb.write32(REG0_CTRL, 0x00000001)
await mon samples = await monitor
assert seen, "W1S bit was never observed on reg0_o[0]" _assert_single_cycle_bit_pulse(samples, bit=0, name="reg_pulse[0][0]")
assert await tb.read32(REG0_CTRL) == 0x00000004 # W1S reads as 0, W1C reset bit remains set
# 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() @cocotb.test()