ITADN

Simulator is writing multiple transition of the same signal at a same timestamp

#1660OpenCryVeck 创建于 2026-04-06
question
C
CryVeckcommented
# Description Amaranth simulator is generating in some cases multiple transition of a signal at one timestamp of a vcd file. I observed that because it generate a visual spike with vaporview (I reported that issue). When I export the amaranth class to verilog and run the same test bench with icarus verilog I do not have thoses multiple transition of a signal at one timestamp. (On the left icarus verilog siulator result; on the right amaranth simulator result; consider the two last signals) <img width="2180" height="474" alt="Image" src="https://github.com/user-attachments/assets/0a88b377-e9e2-4208-9440-89e84e7d804a" /> I tried to reproduce the bug on smaller example but wasn't able to. So I join the code that was causing this issue in our course. I am wondering what could be causing that issue. # Expected behavior Reading the _"IEEE Standard Verilog® Hardware Description Language, year 2001"_ it seems the case of multiple transition of the same signal at one timestamp is not specified (neither allowed nor forbidden). I would expect to take in consideration the transition to the last occurrence in the timestamp, for #1 it would be 1!. (We could even consider previous state was 0, and double transition to X and then 1 at timestamp #1, then we should consider 0->1 transition). In my opinion a vcd file should not contains such pattern. # Configuration `Dockerfile`: ```Dockerfile FROM debian:bullseye WORKDIR /home RUN apt-get -y update &&\ apt-get install -y python3 python3-distutils curl &&\ curl -sSL https://bootstrap.pypa.io/get-pip.py -o get-pip.py &&\ python3 get-pip.py &&\ python3 -m pip install --upgrade 'amaranth[builtin-yosys]' numpy ENV IVERILOG_VERSION=v12-branch RUN apt-get -y update && \ apt-get install -y \ automake \ autoconf \ gperf \ build-essential \ flex \ bison \ git && \ rm -rf /var/lib/apt/lists/* RUN git clone --branch=${IVERILOG_VERSION} https://github.com/steveicarus/iverilog && \ cd iverilog && \ bash autoconf.sh && \ ./configure && \ make && \ make install && \ cd && \ rm -rf iverilog CMD ["/bin/bash"] ``` `pip freeze` ```pip amaranth==0.5.8 amaranth-yosys==0.50.0.0.post122 Jinja2==3.1.6 jschon==0.11.1 MarkupSafe==3.0.3 numpy==2.0.2 packaging==26.0 pyvcd==0.4.1 rfc3986==2.0.0 wasmtime==43.0.0 ``` # Files `main.py` ```python from amaranth import * def is_power_of_two(x): return (x & (x - 1)) == 0 class AdderTree(Elaboratable): def __init__(self, acc_bits, fan_in, signed=True): self.acc_bits = acc_bits self.fan_in = fan_in self.signed = signed assert is_power_of_two(fan_in) assert fan_in >= 2 self.in_data = Array([ Signal(Shape(acc_bits, signed=signed), name=f'in_data_{fan_in}_{i}') for i in range(fan_in)]) self.in_ovf = Array([Signal(1, name=f'in_ovf_{fan_in}_{i}') for i in range(fan_in)]) self.in_valid = Array([Signal(1, name=f'in_valid_{fan_in}_{i}') for i in range(fan_in)]) self.out_d = Signal(Shape(acc_bits, signed=signed), name=f"out_d_{fan_in}") self.out_ovf = Signal(1, name=f"out_ovf_{fan_in}") self.out_valid = Signal(1, name=f"out_valid_{fan_in}") if fan_in > 2: self.tree_l = AdderTree(acc_bits, fan_in // 2, signed) self.tree_r = AdderTree(acc_bits, fan_in // 2, signed) def elaborate(self, platform): m = Module() if self.fan_in > 2: m.submodules.tree_l = tree_l = self.tree_l m.submodules.tree_r = tree_r = self.tree_r half = self.fan_in // 2 for i in range(half): m.d.comb += [ tree_l.in_data[i].eq(self.in_data[i]), tree_l.in_ovf[i].eq(self.in_ovf[i]), tree_l.in_valid[i].eq(self.in_valid[i]), ] for i in range(half): m.d.comb += [ tree_r.in_data[i].eq(self.in_data[half + i]), tree_r.in_ovf[i].eq(self.in_ovf[half + i]), tree_r.in_valid[i].eq(self.in_valid[half + i]), ] sum_result = Signal(Shape(self.acc_bits + 1, signed=self.signed)) m.d.comb += [ sum_result.eq(tree_l.out_d + tree_r.out_d), self.out_d.eq(sum_result[:self.acc_bits]), self.out_valid.eq(tree_l.out_valid & tree_r.out_valid), ] # Overflow detection if self.signed: # Signed overflow: sign bit mismatch after truncation m.d.comb += self.out_ovf.eq( tree_l.out_ovf | tree_r.out_ovf | (sum_result[self.acc_bits] != sum_result[self.acc_bits - 1]) ) else: m.d.comb += self.out_ovf.eq( tree_l.out_ovf | tree_r.out_ovf | sum_result[self.acc_bits] ) else: # Base case: fan_in == 2 sum_result = Signal(Shape(self.acc_bits + 1, signed=self.signed)) m.d.comb += [ sum_result.eq(self.in_data[0] + self.in_data[1]), self.out_d.eq(sum_result[:self.acc_bits]), self.out_valid.eq(self.in_valid[0] & self.in_valid[1]), ] if self.signed: m.d.comb += self.out_ovf.eq( self.in_ovf[0] | self.in_ovf[1] | (sum_result[self.acc_bits] != sum_result[self.acc_bits - 1]) ) else: m.d.comb += self.out_ovf.eq( self.in_ovf[0] | self.in_ovf[1] | sum_result[self.acc_bits] ) return m if __name__ == '__main__': acc_bits = 8 fan_in = 4 signed = True dut = AdderTree(acc_bits=acc_bits, fan_in=fan_in, signed=signed) from amaranth.sim import Simulator, Settle, Delay import numpy as np np.random.seed(42) adder_tree_tb_template = open("adder_tree_tb_template.v", "r").read() tb_def = "" for i in range(fan_in): tb_def += f"reg [7:0] in_data_{fan_in}_{i};\n" tb_def += f"reg in_ovf_{fan_in}_{i};\n" tb_def += f"reg in_valid_{fan_in}_{i};\n" tb_def += f"wire [7:0] out_d_{fan_in};\n" tb_def += f"wire out_ovf_{fan_in};\n" tb_def += f"wire out_valid_{fan_in};\n" port = "" for i in range(fan_in): port += f".in_data_{fan_in}_{i}(in_data_{fan_in}_{i}),\n" port += f".in_ovf_{fan_in}_{i}(in_ovf_{fan_in}_{i}),\n" port += f".in_valid_{fan_in}_{i}(in_valid_{fan_in}_{i}),\n" port += f".out_d_{fan_in}(out_d_{fan_in}),\n" port += f".out_ovf_{fan_in}(out_ovf_{fan_in}),\n" port += f".out_valid_{fan_in}(out_valid_{fan_in})\n" async def test_case(ctx, dut, in_data, in_ovf, in_valid, tb): tb[0] += "#1\n" for i in range(fan_in): tb[0] += f"in_data_{fan_in}_{i} = {in_data[i]};\n" tb[0] += f"in_ovf_{fan_in}_{i} = {in_ovf[i]};\n" tb[0] += f"in_valid_{fan_in}_{i} = {in_valid[i]};\n" for i, d in enumerate(in_data): ctx.set(dut.in_data[i],int(d)) for i, d in enumerate(in_ovf): ctx.set(dut.in_ovf[i],bool(d)) for i, d in enumerate(in_valid): ctx.set(dut.in_valid[i],bool(d)) await ctx.delay(1e-6) hw_sum = ctx.get(dut.out_d) hw_ovf = ctx.get(dut.out_ovf) if hw_ovf: assert ((hw_sum - sum(in_data)) % (2 ** acc_bits) == 0) else: assert (hw_sum == sum(in_data)) async def bench(ctx): tb = [""] for _ in range(512): await test_case(ctx, dut, in_data=np.random.randint( low=-2**(acc_bits-2), high=2**(acc_bits-2), size=fan_in), in_ovf=np.random.randint( low=0, high=1, size=fan_in), in_valid=np.random.randint( low=0, high=2, size=fan_in), tb=tb ) with open("data_for_tb.v", "w") as file_tb: file_tb.write(adder_tree_tb_template.format(dutdef=tb_def, port=port, tb=tb[0])) from pathlib import Path p = Path(__file__) sim = Simulator(dut) sim.add_testbench(bench) with open(p.with_suffix('.vcd'), 'w') as f: with sim.write_vcd(f): sim.run() from amaranth.back import verilog # top = AdderTree(acc_bits=acc_bits, fan_in=fan_in, signed=signed) top = dut with open(p.with_suffix('.v'), 'w') as f: f.write( verilog.convert( top, ports=[*top.in_data, *top.in_ovf, *top.in_valid, top.out_d, top.out_ovf, top.out_valid])) ``` `adder_tree_tb_template.v` ```verilog `timescale 1us / 1fs `include "adder_tree.v" module adder_tree_tb; {dutdef} top dut ( {port} ); initial begin $dumpfile("adder_tree_verilog.vcd"); $dumpvars(0, adder_tree_tb); {tb} $finish; end endmodule ```
3 条评论