24 / 37 · Travaux pratiques
Travaux pratiques : un banc qui signale ses propres échecs
Séparez DUT, stimuli, référence et vérifications pour tester reset, enable et rebouclage sans course.
Les leçons sont gratuites. Inscrivez-vous pour enregistrer vos progrès.
Séparer implémentation et vérificateur
Le banc fournit des stimuli, calcule les attentes et signale les différences. C'est du code de simulation, pas un circuit à synthétiser. Sauvegardez modulo_tick de la leçon paramètres dans modulo_tick.sv et ce banc complet dans tb.sv. Reset synchrone prime; sans enable, count reste et pulse s'efface; avec enable, count progresse modulo N et pulse vaut 1 uniquement au rebouclage. Utilisez une référence entière sans lire l'état interne.
`timescale 1ns/1ps
module tb #(parameter integer N = 5);
localparam integer CW = (N <= 1) ? 1 : $clog2(N);
logic clk = 1'b0;
logic rst = 1'b0;
logic enable = 1'b0;
wire [CW-1:0] count;
wire pulse;
integer expected_count = 0;
logic expected_pulse = 1'b0;
integer checks = 0;
modulo_tick #(.N(N)) dut (
.clk(clk), .rst(rst), .enable(enable),
.count(count), .pulse(pulse)
);
always #5 clk = ~clk;
task automatic step(input logic reset_value, enable_value);
begin
@(negedge clk);
rst = reset_value;
enable = enable_value;
if (reset_value) begin
expected_count = 0;
expected_pulse = 1'b0;
end else begin
expected_pulse = 1'b0;
if (enable_value) begin
expected_count = expected_count + 1;
if (expected_count == N) begin
expected_count = 0;
expected_pulse = 1'b1;
end
end
end
@(posedge clk);
#1;
if (count !== CW'(expected_count) || pulse !== expected_pulse)
$fatal(1, "Mismatch check=%0d count=%0d expected=%0d pulse=%b expected=%b",
checks, count, expected_count, pulse, expected_pulse);
checks = checks + 1;
end
endtask
initial begin
if (N < 1) $fatal(1, "N must be positive");
step(1'b1, 1'b1);
repeat (N-1) step(1'b0, 1'b1);
step(1'b0, 1'b0);
step(1'b0, 1'b1);
step(1'b0, 1'b0);
repeat (2*N+1) step(1'b0, 1'b1);
step(1'b1, 1'b1);
step(1'b0, 1'b0);
$display("PASS N=%0d checks=%0d", N, checks);
$finish;
end
initial begin
#10000;
$fatal(1, "Timeout");
end
endmoduleÉviter une course au front actif
La tâche change les entrées au front descendant, un demi-cycle avant capture, puis compare après les mises à jour non bloquantes du montant. #1 est un retard d'observation pédagogique avec timescale et horloge de 10ns, pas une analyse physique de clock-to-Q, setup ou hold. Les environnements réutilisables peuvent employer clocking blocks ou discipline des régions d'événements. Le premier reset rend le DUT connu; initialiser la référence ne l'initialise pas. !== signale X/Z devant une attente connue.
Compiler avec top explicite
Dans un shell où Icarus est installé:
iverilog -g2012 -s tb -o sim.vvp modulo_tick.sv tb.sv
vvp sim.vvpCompilez séparément avec -P tb.N=1,-P tb.N=2,-P tb.N=5,-P tb.N=8. La documentation officielle Icarus décrit langage et top. Notez la version employée, car le support SystemVerilog varie.
Provoquer volontairement un échec
Modifiez le comparateur pour reboucler un front trop tôt ou retirez l'effacement par défaut de pulse. Un scénario doit échouer avant timeout avec code de sortie non nul. Restaurez ensuite la bonne source. Un PASS incapable de détecter une mutation pertinente est peu probant. Conservez hash, N, commandes, statuts, PASS et première divergence. Un test local n'est pas une exécution DDS:#delay et initial d'un banc général ne correspondent pas automatiquement à l'interface de laboratoire restreinte à une horloge.