new timer

This commit is contained in:
2026-03-01 17:16:44 +01:00
parent 7b46ae5e87
commit abe0668787
19 changed files with 381 additions and 155 deletions

View File

@@ -1,9 +1,6 @@
`timescale 1ns/1ps
module formal_wb_timer #(
parameter WIDTH = 8,
parameter DIVIDER = 0
);
module formal_wb_timer;
(* gclk *) reg i_clk;
(* anyseq *) reg i_rst;
(* anyseq *) reg [31:0] i_wb_adr;
@@ -20,15 +17,14 @@ module formal_wb_timer #(
assign i_wb_rst = 1'b0;
wb_countdown_timer #(
.WIDTH(WIDTH),
.DIVIDER(DIVIDER)
) dut (
wb_countdown_timer dut (
.i_clk(i_clk),
.i_rst(i_rst),
.o_irq(o_irq),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.o_wb_dat(o_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_cyc(i_wb_cyc),
.i_wb_stb(i_wb_stb),
@@ -36,7 +32,7 @@ module formal_wb_timer #(
);
formal_wb_slave_checker #(
.combinatorial_ack(1)
.combinatorial_ack(0)
) wb_checker (
.i_clk(i_clk),
.i_rst(i_rst),

View File

@@ -13,7 +13,7 @@ prove: mode prove
[engines]
bmc: smtbmc yices
cover: smtbmc yices
prove: abc pdr
prove: smtbmc yices
[script]
{{"-formal"|gen_reads}}

View File

@@ -1,74 +1,100 @@
`timescale 1ns/1ps
module wb_countdown_timer #(
parameter WIDTH = 32, // counter width (<=32 makes bus mapping easy)
parameter DIVIDER = 0 // optional prescaler: tick every 2^DIVIDER cycles
parameter address = 32'h00000000 // Base address of peripheral
)(
input wire i_clk,
input wire i_rst,
output reg o_irq,
output wire o_irq,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
output reg [31:0] o_wb_dat,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_cyc,
input wire i_wb_stb,
output wire o_wb_ack
);
// One-cycle acknowledge on any valid WB access
// (classic, zero-wait-state peripheral)
assign o_wb_ack = i_wb_cyc & i_wb_stb;
// Registers
reg [31:0] counter; // The actual counter. Generates an interrupt when it reaches 0
reg [31:0] preload; // The value with which the counter gets loaded after it reaches 0. 0 to keep the timer off
// Internal countdown and prescaler
reg [WIDTH-1:0] counter;
reg [DIVIDER:0] presc; // enough bits to count up to 2^DIVIDER-1
wire tick = (DIVIDER == 0) ? 1'b1 : (presc[DIVIDER] == 1'b1);
reg wb_ack = 0;
reg irq_fired = 0;
reg counter_started = 0;
reg counter_running = 0;
reg prev_counter_running = 0;
assign o_wb_ack = wb_ack;
// Readback: expose the current counter value
always @(*) begin
o_wb_dat = 32'd0;
o_wb_dat[WIDTH-1:0] = counter;
end
assign o_irq = irq_fired;
// Main logic
always @(posedge i_clk) begin
if (i_rst) begin
counter <= {WIDTH{1'b0}};
presc <= { (DIVIDER+1){1'b0} };
o_irq <= 1'b0;
if(i_rst) begin
counter <= 0;
preload <= 0;
wb_ack <= 0;
o_wb_dat <= 0;
irq_fired <= 0;
counter_started <= 0;
counter_running <= 0;
prev_counter_running <= 0;
end else begin
// Default prescaler behavior
if (DIVIDER != 0) begin
if (counter != 0 && !o_irq)
presc <= presc + 1'b1;
else
presc <= { (DIVIDER+1){1'b0} };
end
// Wishbone write: load counter and clear IRQ
if (o_wb_ack && i_wb_we) begin
counter <= i_wb_dat[WIDTH-1:0];
o_irq <= 1'b0;
prev_counter_running <= counter_running;
counter_running <= counter>0;
// reset prescaler on (re)start or stop
presc <= { (DIVIDER+1){1'b0} };
if(!irq_fired && prev_counter_running && !counter_running)
irq_fired <= 1'b1;
if(counter>0 && counter_started)
counter <= counter - 1;
end else begin
// Countdown when running (counter>0), not already IRQ'd
if (!o_irq && counter != 0) begin
if (tick) begin
if (counter == 1) begin
counter <= {WIDTH{1'b0}};
o_irq <= 1'b1; // sticky until next write
presc <= { (DIVIDER+1){1'b0} };
end else begin
counter <= counter - 1'b1;
end
end
if(counter == 0 && preload>0 && counter_started)
counter <= preload;
if(counter == 0 && preload == 0)
counter_started <= 1'b0;
// Ack generation
wb_ack <= i_wb_cyc & i_wb_stb & !wb_ack;
// Read cycle
if(i_wb_cyc && i_wb_stb && !i_wb_we) begin
if(i_wb_adr[3:0] == 4'b0000) begin
if(i_wb_sel[0]) o_wb_dat[7:0] <= counter[7:0];
if(i_wb_sel[1]) o_wb_dat[15:8] <= counter[15:8];
if(i_wb_sel[2]) o_wb_dat[23:16] <= counter[23:16];
if(i_wb_sel[3]) o_wb_dat[31:24] <= counter[31:24];
end else if(i_wb_adr[3:0] == 4'b0100) begin
if(i_wb_sel[0]) o_wb_dat[7:0] <= preload[7:0];
if(i_wb_sel[1]) o_wb_dat[15:8] <= preload[15:8];
if(i_wb_sel[2]) o_wb_dat[23:16] <= preload[23:16];
if(i_wb_sel[3]) o_wb_dat[31:24] <= preload[31:24];
end
end
// write cycle
if(i_wb_cyc && i_wb_stb && i_wb_we) begin
if(i_wb_adr[3:0] == 4'b0000) begin
if(i_wb_sel[0]) counter[7:0] <= i_wb_dat[7:0];
if(i_wb_sel[1]) counter[15:8] <= i_wb_dat[15:8];
if(i_wb_sel[2]) counter[23:16] <= i_wb_dat[23:16];
if(i_wb_sel[3]) counter[31:24] <= i_wb_dat[31:24];
counter_started <= 1'b1;
end else if(i_wb_adr[3:0] == 4'b0100) begin
if(i_wb_sel[0]) preload[7:0] <= i_wb_dat[7:0];
if(i_wb_sel[1]) preload[15:8] <= i_wb_dat[15:8];
if(i_wb_sel[2]) preload[23:16] <= i_wb_dat[23:16];
if(i_wb_sel[3]) preload[31:24] <= i_wb_dat[31:24];
counter_started <= 1'b1;
end else if(i_wb_adr[3:0] == 4'b1000) begin
// Any write to BASE+8 will ack the IRQ
irq_fired <= 1'b0;
end
end
end
end
endmodule
endmodule

View File

@@ -0,0 +1,143 @@
`timescale 1ns/1ps
module tb_wb_timer;
localparam ADDR_COUNTER = 32'h0000_0000;
localparam ADDR_PRELOAD = 32'h0000_0004;
localparam ADDR_ACK = 32'h0000_0008;
reg i_clk;
reg i_rst;
reg [31:0] i_wb_adr;
reg [31:0] i_wb_dat;
reg [3:0] i_wb_sel;
reg i_wb_we;
reg i_wb_stb;
reg i_wb_cyc;
wire [31:0] o_wb_dat;
wire o_wb_ack;
wire o_irq;
reg [31:0] read_data;
integer cycle;
wb_countdown_timer dut (
.i_clk(i_clk),
.i_rst(i_rst),
.o_irq(o_irq),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.o_wb_dat(o_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_cyc(i_wb_cyc),
.i_wb_stb(i_wb_stb),
.o_wb_ack(o_wb_ack)
);
initial i_clk = 1'b0;
always #5 i_clk = ~i_clk;
task automatic wb_write;
input [31:0] addr;
input [31:0] data;
input [3:0] sel;
begin
@(negedge i_clk);
i_wb_adr <= addr;
i_wb_dat <= data;
i_wb_sel <= sel;
i_wb_we <= 1'b1;
i_wb_stb <= 1'b1;
i_wb_cyc <= 1'b1;
while (!o_wb_ack)
@(posedge i_clk);
@(negedge i_clk);
i_wb_we <= 1'b0;
i_wb_stb <= 1'b0;
i_wb_cyc <= 1'b0;
i_wb_sel <= 4'b0000;
i_wb_dat <= 32'h0000_0000;
end
endtask
task automatic wb_read;
input [31:0] addr;
output [31:0] data;
begin
@(negedge i_clk);
i_wb_adr <= addr;
i_wb_dat <= 32'h0000_0000;
i_wb_sel <= 4'b1111;
i_wb_we <= 1'b0;
i_wb_stb <= 1'b1;
i_wb_cyc <= 1'b1;
while (!o_wb_ack)
@(posedge i_clk);
#1;
data = o_wb_dat;
@(negedge i_clk);
i_wb_stb <= 1'b0;
i_wb_cyc <= 1'b0;
i_wb_sel <= 4'b0000;
end
endtask
initial begin
$dumpfile("wb_timer.vcd");
$dumpvars(0, tb_wb_timer);
i_rst = 1'b1;
i_wb_adr = 32'h0000_0000;
i_wb_dat = 32'h0000_0000;
i_wb_sel = 4'b0000;
i_wb_we = 1'b0;
i_wb_stb = 1'b0;
i_wb_cyc = 1'b0;
repeat (2) @(posedge i_clk);
i_rst = 1'b0;
wb_write(ADDR_COUNTER, 5, 4'b1111);
wb_write(ADDR_PRELOAD, 0, 4'b1111);
for (cycle = 0; cycle < 40; cycle = cycle + 1) begin
@(posedge i_clk);
if(o_irq)
wb_write(ADDR_ACK, 32'h0000_ffff, 4'b1111);
end
for (cycle = 0; cycle < 8; cycle = cycle + 1)
@(posedge i_clk);
wb_write(ADDR_PRELOAD, 8, 4'b1111);
for (cycle = 0; cycle < 21; cycle = cycle + 1) begin
@(posedge i_clk);
if(o_irq)
wb_write(ADDR_ACK, 32'h0000_ffff, 4'b1111);
end
wb_write(ADDR_PRELOAD, 6, 4'b1111);
for (cycle = 0; cycle < 21; cycle = cycle + 1) begin
@(posedge i_clk);
if(o_irq)
wb_write(ADDR_ACK, 32'h0000_ffff, 4'b1111);
end
wb_write(ADDR_PRELOAD, 0, 4'b1111);
for (cycle = 0; cycle < 10; cycle = cycle + 1) begin
@(posedge i_clk);
if(o_irq)
wb_write(ADDR_ACK, 32'h0000_ffff, 4'b1111);
end
$finish;
end
endmodule

View File

@@ -8,6 +8,10 @@ filesets:
files:
- rtl/wb_timer.v
file_type: verilogSource
tb:
files:
- tb/tb_wb_timer.v
file_type: verilogSource
formal_rtl:
depend:
- joppeb:wb:formal_checker
@@ -25,8 +29,13 @@ targets:
- rtl
toplevel: wb_countdown_timer
parameters:
- WIDTH
- DIVIDER
- address
sim:
default_tool: icarus
filesets:
- rtl
- tb
toplevel: tb_wb_timer
formal:
default_tool: symbiyosys
filesets:
@@ -35,15 +44,10 @@ targets:
- formal_cfg
toplevel: formal_wb_timer
parameters:
- WIDTH
- DIVIDER
- address
parameters:
WIDTH:
address:
datatype: int
description: Counter width in bits
paramtype: vlogparam
DIVIDER:
datatype: int
description: Prescaler divider as a power of two exponent
description: Base address of register set
paramtype: vlogparam