Added some stuff from modem and added formal

This commit is contained in:
2026-02-28 18:23:39 +01:00
parent fa641b1eab
commit cf7e03b9fe
55 changed files with 3717 additions and 31 deletions

View File

@@ -0,0 +1,73 @@
`timescale 1ns/1ps
module formal_wb_master_checker (
input wire i_clk,
input wire i_rst,
input wire i_wb_rst,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_stb,
input wire i_wb_cyc,
input wire [31:0] o_wb_rdt,
input wire o_wb_ack
);
reg f_past_valid;
initial f_past_valid = 1'b0;
always @(posedge i_clk) begin
f_past_valid <= 1'b1;
// A1: Slave ACK must correspond to either a same-cycle or previous-cycle request
if(o_wb_ack)
assume(
(i_wb_cyc && i_wb_stb) ||
(f_past_valid && $past(i_wb_cyc && i_wb_stb))
);
// A2: Slave must not ACK outside an active cycle
if(!i_wb_cyc)
assume(!o_wb_ack);
// A3: Once STB has been low for a full cycle, slave ACK must be low
if(
f_past_valid &&
!$past(i_wb_stb) &&
!i_wb_stb
)
assume(!o_wb_ack);
// R1: Reset must leave the master initialized on the following cycle
if(f_past_valid && $past(i_rst || i_wb_rst)) begin
assert(!i_wb_cyc);
assert(!i_wb_stb);
end
// R2: STB never high without CYC
if(i_wb_stb)
assert(i_wb_cyc);
// R3: Once a request starts, hold it stable until the slave responds
if(
f_past_valid &&
$past(i_wb_cyc && i_wb_stb && !o_wb_ack) &&
!o_wb_ack &&
!(i_rst || i_wb_rst)
) begin
assert(i_wb_cyc);
assert(i_wb_stb);
assert(i_wb_adr == $past(i_wb_adr));
assert(i_wb_dat == $past(i_wb_dat));
assert(i_wb_sel == $past(i_wb_sel));
assert(i_wb_we == $past(i_wb_we));
end
// R4: Once CYC is low, STB must also be low
if(!i_wb_cyc)
assert(!i_wb_stb);
end
wire unused = &{1'b0, o_wb_rdt};
endmodule

View File

@@ -0,0 +1,68 @@
`timescale 1ns/1ps
module formal_wb_slave_checker (
input wire i_clk,
input wire i_rst,
input wire i_wb_rst,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_stb,
input wire i_wb_cyc,
input wire [31:0] o_wb_rdt,
input wire o_wb_ack
);
reg f_past_valid;
initial f_past_valid = 1'b0;
always @(posedge i_clk) begin
f_past_valid <= 1'b1;
// A1: Reset forces cyc=0, stb=0
if (i_rst) begin
assume(!i_wb_cyc);
assume(!i_wb_stb);
end
// A2: std->cyc, stb never high without cyc
if(i_wb_stb)
assume(i_wb_cyc);
// A3: once a request starts, hold it stable until the slave responds
if(f_past_valid && $past(i_wb_cyc && i_wb_stb && !o_wb_ack)) begin
assume(i_wb_cyc);
assume(i_wb_stb);
assume(i_wb_adr == $past(i_wb_adr));
assume(i_wb_dat == $past(i_wb_dat));
assume(i_wb_sel == $past(i_wb_sel));
assume(i_wb_we == $past(i_wb_we));
end
// R1: ACK must correspond to either a same-cycle or previous-cycle request
if(o_wb_ack)
assert(
(i_wb_cyc && i_wb_stb) ||
(f_past_valid && $past(i_wb_cyc && i_wb_stb))
);
// R2: !CYC->!ACK : no ghost acks
if(!i_wb_cyc)
assert(!o_wb_ack);
// R3: Reset must leave the slave initialized on the following cycle
if(f_past_valid && $past(i_rst || i_wb_rst))
assert(!o_wb_ack);
// R4: once STB has been dropped for a full cycle, ACK must be low
if(
f_past_valid &&
!$past(i_wb_stb) &&
!i_wb_stb
)
assert(!o_wb_ack);
end
wire unused = &{1'b0, o_wb_rdt};
endmodule

View File

@@ -0,0 +1,16 @@
CAPI=2:
name: joppeb:wb:formal_checker:1.0
description: Reusable formal Wishbone protocol checkers
filesets:
formal_rtl:
files:
- formal/formal_wb_slave_checker.v
- formal/formal_wb_master_checker.v
file_type: verilogSource
targets:
default:
filesets:
- formal_rtl

View File

@@ -0,0 +1,63 @@
`timescale 1ns/1ps
module formal_jtag_wb_bridge;
(* gclk *) reg i_clk;
(* anyseq *) reg i_rst;
(* anyseq *) reg [31:0] i_wb_rdt;
(* anyseq *) reg i_wb_ack;
reg f_past_valid;
wire [31:0] o_wb_adr;
wire [31:0] o_wb_dat;
wire [3:0] o_wb_sel;
wire o_wb_we;
wire o_wb_cyc;
wire o_wb_stb;
wire o_cmd_reset;
// This bridge has no dedicated Wishbone reset output.
wire f_wb_rst = 1'b0;
jtag_wb_bridge #(
.chain(1),
.byte_aligned(0)
) dut (
.i_clk(i_clk),
.i_rst(i_rst),
.o_wb_adr(o_wb_adr),
.o_wb_dat(o_wb_dat),
.o_wb_sel(o_wb_sel),
.o_wb_we(o_wb_we),
.o_wb_cyc(o_wb_cyc),
.o_wb_stb(o_wb_stb),
.i_wb_rdt(i_wb_rdt),
.i_wb_ack(i_wb_ack),
.o_cmd_reset(o_cmd_reset)
);
formal_wb_master_checker wb_checker (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(f_wb_rst),
.i_wb_adr(o_wb_adr),
.i_wb_dat(o_wb_dat),
.i_wb_sel(o_wb_sel),
.i_wb_we(o_wb_we),
.i_wb_stb(o_wb_stb),
.i_wb_cyc(o_wb_cyc),
.o_wb_rdt(i_wb_rdt),
.o_wb_ack(i_wb_ack)
);
initial f_past_valid = 1'b0;
always @(posedge i_clk) begin
f_past_valid <= 1'b1;
// A1: Start in reset so the bridge state is initialized
if (!f_past_valid)
assume(i_rst);
end
wire unused = &{1'b0, o_cmd_reset};
endmodule

View File

@@ -0,0 +1,14 @@
[options]
mode prove
depth 8
[engines]
smtbmc z3
[script]
{{"-formal"|gen_reads}}
prep -top {{top_level}}
clk2fflogic
[files]
{{files}}

View File

@@ -0,0 +1,63 @@
`timescale 1ns/1ps
module cdc_req_resp #(
parameter integer REQ_W = 32,
parameter integer RESP_W = 32,
parameter integer STABLE_SAMPLES = 2
)(
input wire a_clk,
input wire a_rst,
input wire a_req_pulse,
input wire [REQ_W-1:0] a_req_data,
output wire a_req_busy,
output wire a_req_accepted,
output wire a_resp_pulse,
output wire [RESP_W-1:0] a_resp_data,
input wire b_clk,
input wire b_rst,
output reg b_req_pulse,
output reg [REQ_W-1:0] b_req_data,
input wire b_resp_pulse,
input wire [RESP_W-1:0] b_resp_data,
output wire b_resp_busy,
output wire b_resp_accepted
);
(* anyseq *) reg f_req_pulse;
(* anyseq *) reg [REQ_W-1:0] f_req_data;
reg f_past_valid;
assign a_req_busy = 1'b0;
assign a_req_accepted = 1'b0;
assign a_resp_pulse = 1'b0;
assign a_resp_data = {RESP_W{1'b0}};
assign b_resp_busy = 1'b0;
assign b_resp_accepted = 1'b0;
initial f_past_valid = 1'b0;
always @(posedge b_clk) begin
f_past_valid <= 1'b1;
b_req_pulse <= f_req_pulse;
b_req_data <= f_req_data;
// A1: no abstract request while system reset is asserted
if (b_rst)
assume(!f_req_pulse);
// A2: abstract requests are single-cycle pulses
if (f_past_valid && $past(f_req_pulse))
assume(!f_req_pulse);
end
wire unused = &{
1'b0,
a_clk,
a_rst,
a_req_pulse,
a_req_data,
b_resp_pulse,
b_resp_data,
STABLE_SAMPLES[0]
};
endmodule

View File

@@ -0,0 +1,28 @@
`timescale 1ns/1ps
module jtag_if #(
parameter chain = 1
)(
input wire i_tdo,
output wire o_tck,
output wire o_tdi,
output wire o_drck,
output wire o_capture,
output wire o_shift,
output wire o_update,
output wire o_runtest,
output wire o_reset,
output wire o_sel
);
assign o_tck = 1'b0;
assign o_tdi = 1'b0;
assign o_drck = 1'b0;
assign o_capture = 1'b0;
assign o_shift = 1'b0;
assign o_update = 1'b0;
assign o_runtest = 1'b0;
assign o_reset = 1'b0;
assign o_sel = 1'b0;
wire unused = &{1'b0, i_tdo, chain[0]};
endmodule

View File

@@ -0,0 +1,64 @@
CAPI=2:
name: joppeb:wb:jtag_wb_bridge:1.0
description: Generic JTAG boundary scan to Wishbone classic bridge
filesets:
rtl:
depend:
- joppeb:primitive:jtag_if
- joppeb:util:cdc
files:
- rtl/jtag_wb_bridge.v
file_type: verilogSource
formal_rtl:
depend:
- joppeb:wb:formal_checker
files:
- formal/formal_jtag_wb_bridge.v
file_type: verilogSource
formal_abstract_rtl:
depend:
- joppeb:wb:formal_checker
files:
- rtl/jtag_wb_bridge.v
- formal/stub_jtag_if.v
- formal/stub_cdc_req_resp.v
- formal/formal_jtag_wb_bridge.v
file_type: verilogSource
formal_cfg:
files:
- formal/jtag_wb_bridge.sby
file_type: sbyConfigTemplate
targets:
default:
filesets:
- rtl
toplevel: jtag_wb_bridge
parameters:
- chain
- byte_aligned
formal:
default_tool: symbiyosys
filesets:
- rtl
- formal_rtl
- formal_cfg
toplevel: formal_jtag_wb_bridge
formal_abstract:
default_tool: symbiyosys
filesets:
- formal_abstract_rtl
- formal_cfg
toplevel: formal_jtag_wb_bridge
parameters:
chain:
datatype: int
description: User chain
paramtype: vlogparam
byte_aligned:
datatype: int
description: use addr[1:0] for byte lane on 32-bit WB when 0, always use lane 0 when 1
paramtype: vlogparam

View File

@@ -0,0 +1,538 @@
`timescale 1 ns/1 ps
module jtag_wb_bridge #(
parameter integer chain = 1,
// 0: use addr[1:0] for byte lane on 32-bit WB
// 1: always use lane 0
parameter integer byte_aligned = 0
)(
input wire i_clk,
input wire i_rst,
output wire [31:0] o_wb_adr,
output wire [31:0] o_wb_dat,
output wire [3:0] o_wb_sel,
output wire o_wb_we,
output wire o_wb_cyc,
output wire o_wb_stb,
input wire [31:0] i_wb_rdt,
input wire i_wb_ack,
output wire o_cmd_reset
);
// ===========================================================================
// JTAG interface (Spartan-6 BSCAN wrapper)
// ===========================================================================
wire jtag_tck;
wire jtag_tdi;
wire jtag_drck;
wire jtag_capture;
wire jtag_shift;
wire jtag_update;
wire jtag_runtest;
wire jtag_reset;
wire jtag_sel;
localparam integer JTAG_DR_W = 72;
// 72-bit DR (symmetrical command/response)
// Command layout: [71:64] opcode, [63:32] addr, [31:0] data
// Response layout: [71:64] resp_seq, [63:56] status, [55:48] cmd_seq,
// [47:16] data, [15:8] flags, [7:0] last_op
reg [JTAG_DR_W-1:0] jtag_shreg;
jtag_if #(
.chain(chain)
) u_jtag (
.i_tdo(jtag_shreg[0]),
.o_tck(jtag_tck),
.o_tdi(jtag_tdi),
.o_drck(jtag_drck),
.o_capture(jtag_capture),
.o_shift(jtag_shift),
.o_update(jtag_update),
.o_runtest(jtag_runtest),
.o_reset(jtag_reset),
.o_sel(jtag_sel)
);
wire jtag_async_reset = jtag_reset || i_rst;
// ===========================================================================
// CDC request/response channel (72/72 symmetric)
// Side A: JTAG/TCK domain
// Side B: system/i_clk domain
// ===========================================================================
wire a_req_busy;
wire a_req_accepted;
wire a_resp_pulse;
wire [JTAG_DR_W-1:0] a_resp_data;
wire b_req_pulse;
wire [JTAG_DR_W-1:0] b_req_data;
reg b_resp_pulse;
reg [JTAG_DR_W-1:0] b_resp_data;
wire b_resp_busy;
wire b_resp_accepted;
// Accept UPDATE as a request strobe (qualified by SEL and !busy)
wire a_req_pulse = jtag_sel && jtag_update && !a_req_busy;
wire [JTAG_DR_W-1:0] a_req_data = jtag_shreg;
cdc_req_resp #(
.REQ_W(JTAG_DR_W),
.RESP_W(JTAG_DR_W),
.STABLE_SAMPLES(2)
) u_cdc (
.a_clk(jtag_tck),
.a_rst(jtag_async_reset),
.a_req_pulse(a_req_pulse),
.a_req_data(a_req_data),
.a_req_busy(a_req_busy),
.a_req_accepted(a_req_accepted),
.a_resp_pulse(a_resp_pulse),
.a_resp_data(a_resp_data),
.b_clk(i_clk),
.b_rst(i_rst),
.b_req_pulse(b_req_pulse),
.b_req_data(b_req_data),
.b_resp_pulse(b_resp_pulse),
.b_resp_data(b_resp_data),
.b_resp_busy(b_resp_busy),
.b_resp_accepted(b_resp_accepted)
);
// ===========================================================================
// JTAG/TCK domain shift/capture
// ===========================================================================
reg [JTAG_DR_W-1:0] resp_hold_tck;
always @(posedge jtag_tck or posedge jtag_async_reset) begin
if (jtag_async_reset) begin
jtag_shreg <= {JTAG_DR_W{1'b0}};
resp_hold_tck <= {JTAG_DR_W{1'b0}};
end else begin
// Latch new response word from CDC when it arrives (independent of CAPTURE)
if (a_resp_pulse) begin
resp_hold_tck <= a_resp_data;
end
if (jtag_sel && jtag_capture) begin
// Load response into shift register for host readout
jtag_shreg <= resp_hold_tck;
end else if (jtag_sel && jtag_shift) begin
// Shift: MSB in, LSB out to TDO
jtag_shreg <= {jtag_tdi, jtag_shreg[JTAG_DR_W-1:1]};
end
end
end
// ===========================================================================
// System domain: Wishbone master + small command queue + response pending
// ===========================================================================
// Opcodes
localparam [7:0] OP_NOP = 8'h00;
localparam [7:0] OP_RESET_ON = 8'h10;
localparam [7:0] OP_RESET_OFF = 8'h11;
localparam [7:0] OP_WRITE8 = 8'h20;
localparam [7:0] OP_READ8 = 8'h21;
localparam [7:0] OP_WRITE32 = 8'h22;
localparam [7:0] OP_READ32 = 8'h23;
localparam [7:0] OP_PING = 8'h30;
localparam [7:0] OP_CLEAR_FLAGS = 8'h40;
// Wishbone regs
reg wb_busy;
reg [31:0] wb_adr_r;
reg [31:0] wb_dat_r;
reg [3:0] wb_sel_r;
reg wb_we_r;
assign o_wb_adr = wb_adr_r;
assign o_wb_dat = wb_dat_r;
assign o_wb_sel = wb_sel_r;
assign o_wb_we = wb_we_r;
assign o_wb_cyc = wb_busy;
assign o_wb_stb = wb_busy;
// Reset control
reg cmd_reset_level_r;
assign o_cmd_reset = cmd_reset_level_r;
// For reporting only: sync a_req_busy (TCK domain) into i_clk
(* ASYNC_REG="TRUE" *) reg req_busy_sync1, req_busy_sync2;
wire req_busy_tck_sync = req_busy_sync2;
// Sequencing
reg [7:0] cmd_seq_r;
reg [7:0] resp_seq_r;
// Sticky flags (cleared by CLEAR_FLAGS or reset)
reg flag_cmd_overflow;
reg flag_illegal;
reg flag_wb_busy_at_req;
// Snapshot info
reg last_we_r;
reg [7:0] last_opcode_r;
// Active command / queued command
reg act_valid;
reg [7:0] act_opcode;
reg [31:0] act_addr;
reg [31:0] act_data;
reg [7:0] act_seq;
reg q_valid;
reg [7:0] q_opcode;
reg [31:0] q_addr;
reg [31:0] q_data;
reg [7:0] q_seq;
// Response pending buffer (to avoid dropping if resp mailbox busy)
reg resp_pending;
reg [JTAG_DR_W-1:0] resp_pending_word;
// Lane selection
wire [1:0] addr_lane = byte_aligned ? 2'b00 : act_addr[1:0];
// Helpers: form SEL/DAT for byte write
function [3:0] sel_from_lane(input [1:0] lane);
case (lane)
2'b00: sel_from_lane = 4'b0001;
2'b01: sel_from_lane = 4'b0010;
2'b10: sel_from_lane = 4'b0100;
default: sel_from_lane = 4'b1000;
endcase
endfunction
function [31:0] dat_from_lane_byte(input [1:0] lane, input [7:0] b);
case (lane)
2'b00: dat_from_lane_byte = {24'b0, b};
2'b01: dat_from_lane_byte = {16'b0, b, 8'b0};
2'b10: dat_from_lane_byte = {8'b0, b, 16'b0};
default: dat_from_lane_byte = {b, 24'b0};
endcase
endfunction
function [7:0] byte_from_lane(input [1:0] lane, input [31:0] w);
case (lane)
2'b00: byte_from_lane = w[7:0];
2'b01: byte_from_lane = w[15:8];
2'b10: byte_from_lane = w[23:16];
default: byte_from_lane = w[31:24];
endcase
endfunction
// Build response word
function [JTAG_DR_W-1:0] pack_resp(
input [7:0] resp_seq,
input [7:0] status,
input [7:0] cmd_seq,
input [31:0] data,
input [7:0] flags,
input [7:0] last_op
);
pack_resp = {resp_seq, status, cmd_seq, data, flags, last_op};
endfunction
// STATUS bits (snapshot)
wire [7:0] status_snapshot = {
2'b00, // [7:6]
1'b1, // [5] resp_valid
last_we_r, // [4] last_we
cmd_reset_level_r, // [3] reset_level
b_resp_busy, // [2] resp_busy (system domain)
req_busy_tck_sync, // [1] req_busy (synced from TCK just for reporting)
wb_busy // [0] wb_busy
};
// FLAGS bits (sticky)
wire [7:0] flags_sticky = {
4'b0000, // [7:4] reserved
1'b0, // [3] reserved
flag_wb_busy_at_req, // [2]
flag_illegal, // [1]
flag_cmd_overflow // [0]
};
// Queue a command (or set overflow sticky if queue full)
task automatic enqueue_cmd(
input [7:0] op,
input [31:0] addr,
input [31:0] dat,
input [7:0] seq
);
begin
if (!q_valid) begin
q_valid <= 1'b1;
q_opcode <= op;
q_addr <= addr;
q_data <= dat;
q_seq <= seq;
end else begin
// Already have one queued; mark overflow and drop this command
flag_cmd_overflow <= 1'b1;
end
end
endtask
// Start executing a command (for non-WB ops may immediately create a response)
task automatic start_active_cmd(
input [7:0] cmd_opcode,
input [31:0] cmd_addr,
input [31:0] cmd_data,
input [7:0] cmd_seq
);
reg [1:0] cmd_addr_lane;
begin
cmd_addr_lane = byte_aligned ? 2'b00 : cmd_addr[1:0];
last_opcode_r <= cmd_opcode;
last_we_r <= (cmd_opcode == OP_WRITE8) || (cmd_opcode == OP_WRITE32);
// If we're already mid-flight or holding a response, note it (diagnostic)
if (wb_busy || resp_pending)
flag_wb_busy_at_req <= 1'b1;
case (cmd_opcode)
OP_NOP: begin
// immediate response
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_PING: begin
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'hA5, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_CLEAR_FLAGS: begin
flag_cmd_overflow <= 1'b0;
flag_illegal <= 1'b0;
flag_wb_busy_at_req <= 1'b0;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, 8'h00, cmd_opcode);
resp_pending <= 1'b1;
end
OP_RESET_ON: begin
cmd_reset_level_r <= 1'b1;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_RESET_OFF: begin
cmd_reset_level_r <= 1'b0;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_WRITE8: begin
// launch WB write (byte)
wb_busy <= 1'b1;
wb_we_r <= 1'b1;
wb_adr_r <= cmd_addr;
wb_sel_r <= sel_from_lane(cmd_addr_lane);
wb_dat_r <= dat_from_lane_byte(cmd_addr_lane, cmd_data[7:0]);
end
OP_READ8: begin
// launch WB read (byte select)
wb_busy <= 1'b1;
wb_we_r <= 1'b0;
wb_adr_r <= cmd_addr;
wb_sel_r <= sel_from_lane(cmd_addr_lane);
wb_dat_r <= 32'b0;
end
OP_WRITE32: begin
// launch WB write (full word)
wb_busy <= 1'b1;
wb_we_r <= 1'b1;
wb_adr_r <= cmd_addr;
wb_sel_r <= 4'b1111;
wb_dat_r <= cmd_data;
end
OP_READ32: begin
// launch WB read (full word)
wb_busy <= 1'b1;
wb_we_r <= 1'b0;
wb_adr_r <= cmd_addr;
wb_sel_r <= 4'b1111;
wb_dat_r <= 32'b0;
end
default: begin
flag_illegal <= 1'b1;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
endcase
end
endtask
// System main
always @(posedge i_clk) begin
if (i_rst) begin
wb_busy <= 1'b0;
wb_adr_r <= 32'b0;
wb_dat_r <= 32'b0;
wb_sel_r <= 4'b0000;
wb_we_r <= 1'b0;
cmd_reset_level_r<= 1'b0;
req_busy_sync1 <= 1'b0;
req_busy_sync2 <= 1'b0;
cmd_seq_r <= 8'd0;
resp_seq_r <= 8'd0;
flag_cmd_overflow<= 1'b0;
flag_illegal <= 1'b0;
flag_wb_busy_at_req <= 1'b0;
last_we_r <= 1'b0;
last_opcode_r <= 8'h00;
act_valid <= 1'b0;
act_opcode <= 8'h00;
act_addr <= 32'h0;
act_data <= 32'h0000_0000;
act_seq <= 8'h00;
q_valid <= 1'b0;
q_opcode <= 8'h00;
q_addr <= 32'h0;
q_data <= 32'h0000_0000;
q_seq <= 8'h00;
resp_pending <= 1'b0;
resp_pending_word<= {JTAG_DR_W{1'b0}};
b_resp_pulse <= 1'b0;
b_resp_data <= {JTAG_DR_W{1'b0}};
end else begin
b_resp_pulse <= 1'b0;
// Sync req-busy level (reporting only)
req_busy_sync1 <= a_req_busy;
req_busy_sync2 <= req_busy_sync1;
// -----------------------------------------------------------------------
// Accept incoming command from CDC (always delivered; we buffer internally)
// -----------------------------------------------------------------------
if (b_req_pulse) begin
// assign a sequence number to each received command
cmd_seq_r <= cmd_seq_r + 8'd1;
// If we can start immediately (no active, no wb, no pending response), do so.
if (!act_valid && !wb_busy && !resp_pending) begin
act_valid <= 1'b1;
act_opcode <= b_req_data[71:64];
act_addr <= b_req_data[63:32];
act_data <= b_req_data[31:0];
act_seq <= cmd_seq_r;
// Start it right away
start_active_cmd(b_req_data[71:64], b_req_data[63:32], b_req_data[31:0], cmd_seq_r);
end else begin
// Otherwise enqueue one-deep
enqueue_cmd(b_req_data[71:64], b_req_data[63:32], b_req_data[31:0], cmd_seq_r);
end
end
// -----------------------------------------------------------------------
// Wishbone completion -> create response (but don't drop; buffer pending)
// -----------------------------------------------------------------------
if (wb_busy && i_wb_ack) begin
wb_busy <= 1'b0;
wb_we_r <= 1'b0;
// Determine response data
case (act_opcode)
OP_READ8: begin
resp_pending_word <= pack_resp(
resp_seq_r,
status_snapshot,
act_seq,
{24'b0, byte_from_lane(addr_lane, i_wb_rdt)},
flags_sticky,
act_opcode
);
end
OP_READ32: begin
resp_pending_word <= pack_resp(
resp_seq_r,
status_snapshot,
act_seq,
i_wb_rdt,
flags_sticky,
act_opcode
);
end
default: begin
// WRITE8/WRITE32: echo written data
resp_pending_word <= pack_resp(
resp_seq_r,
status_snapshot,
act_seq,
act_data,
flags_sticky,
act_opcode
);
end
endcase
resp_pending <= 1'b1;
end
// -----------------------------------------------------------------------
// If we have a pending response and response mailbox is free, send it
// -----------------------------------------------------------------------
if (resp_pending && !b_resp_busy) begin
b_resp_data <= resp_pending_word;
b_resp_pulse <= 1'b1;
resp_pending <= 1'b0;
resp_seq_r <= resp_seq_r + 8'd1;
// Mark active command complete
act_valid <= 1'b0;
// If there is a queued command, promote and start it
if (q_valid) begin
act_valid <= 1'b1;
act_opcode <= q_opcode;
act_addr <= q_addr;
act_data <= q_data;
act_seq <= q_seq;
q_valid <= 1'b0;
start_active_cmd(q_opcode, q_addr, q_data, q_seq);
end
end
// -----------------------------------------------------------------------
// If no active command but there is a queued one (and we're not busy), start it
// -----------------------------------------------------------------------
if (!act_valid && q_valid && !wb_busy && !resp_pending) begin
act_valid <= 1'b1;
act_opcode <= q_opcode;
act_addr <= q_addr;
act_data <= q_data;
act_seq <= q_seq;
q_valid <= 1'b0;
start_active_cmd(q_opcode, q_addr, q_data, q_seq);
end
end
end
endmodule

View File

@@ -0,0 +1,6 @@
__pycache__/
*.d
*.o
*.a
*.so
prog

View File

@@ -0,0 +1,43 @@
TOOLCHAIN_PREFIX ?=
CXX := $(TOOLCHAIN_PREFIX)g++
AR := $(TOOLCHAIN_PREFIX)ar
TARGET := prog
STATIC_LIB := libjtag_wb_bridge.a
SHARED_LIB := libjtag_wb_bridge.so
ADEPT_LIBDIR := /opt/packages/digilent.adept.runtime_2.27.9-x86_64/lib64
CPPFLAGS := -MMD -MP
CXXFLAGS := -O2 -Wall -Wextra -std=c++17 -fPIC
LDFLAGS := -L$(ADEPT_LIBDIR) -Wl,--disable-new-dtags -Wl,-rpath,$(ADEPT_LIBDIR)
LIBS := -ldjtg -ldmgr -ldpcomm -ldabs -ldftd2xx
LIB_SRCS := difilent_jtag.cpp jtag_wb_bridge_client.cpp jtag_wb_bridge_c.cpp
APP_SRCS := prog.cpp argparse.cpp
LIB_OBJS := $(LIB_SRCS:.cpp=.o)
APP_OBJS := $(APP_SRCS:.cpp=.o)
DEPS := $(LIB_OBJS:.o=.d) $(APP_OBJS:.o=.d)
.PHONY: all clean
all: $(STATIC_LIB) $(SHARED_LIB) $(TARGET)
$(STATIC_LIB): $(LIB_OBJS)
$(AR) rcs $@ $(LIB_OBJS)
$(SHARED_LIB): $(LIB_OBJS)
$(CXX) -shared $(LDFLAGS) -o $@ $(LIB_OBJS) $(LIBS)
$(TARGET): $(APP_OBJS) $(STATIC_LIB)
$(CXX) $(LDFLAGS) -o $@ $(APP_OBJS) -L. -ljtag_wb_bridge $(LIBS)
%.o: %.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
clean:
rm -f $(TARGET) $(STATIC_LIB) $(SHARED_LIB) $(LIB_OBJS) $(APP_OBJS) $(DEPS)
-include $(DEPS)

View File

@@ -0,0 +1,285 @@
#include "argparse.hpp"
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <sstream>
ArgParser::ArgParser(std::string program_name)
: program_name_(std::move(program_name)) {}
void ArgParser::addString(const std::string &name,
const std::string &default_value,
const std::string &help,
bool required,
const std::string &short_name) {
order_.push_back(name);
meta_[name] = {OptionType::kString, help, required, short_name};
string_values_[name] = default_value;
provided_[name] = false;
if (!short_name.empty()) {
short_to_long_[short_name] = name;
}
}
void ArgParser::addInt(const std::string &name,
int default_value,
const std::string &help,
bool required,
const std::string &short_name) {
order_.push_back(name);
meta_[name] = {OptionType::kInt, help, required, short_name};
int_values_[name] = default_value;
provided_[name] = false;
if (!short_name.empty()) {
short_to_long_[short_name] = name;
}
}
void ArgParser::addFlag(const std::string &name,
const std::string &help,
const std::string &short_name) {
order_.push_back(name);
meta_[name] = {OptionType::kFlag, help, false, short_name};
flag_values_[name] = false;
provided_[name] = false;
if (!short_name.empty()) {
short_to_long_[short_name] = name;
}
}
bool ArgParser::parse(int argc, char **argv, std::string *error) {
for (int i = 1; i < argc; ++i) {
std::string token(argv[i]);
if (token == "--help" || token == "-h") {
if (error) {
*error = "help";
}
return false;
}
if (token.rfind("--", 0) != 0 && token.rfind("-", 0) == 0) {
std::string short_key = token.substr(1);
std::string short_value;
size_t short_eq = short_key.find('=');
if (short_eq != std::string::npos) {
short_value = short_key.substr(short_eq + 1);
short_key = short_key.substr(0, short_eq);
}
auto sk = short_to_long_.find(short_key);
if (sk == short_to_long_.end()) {
if (error) {
*error = "Unknown option: -" + short_key;
}
return false;
}
auto m = meta_.find(sk->second);
if (m == meta_.end()) {
if (error) {
*error = "Unknown option: -" + short_key;
}
return false;
}
if (m->second.type == OptionType::kFlag) {
if (short_eq != std::string::npos) {
if (error) {
*error = "Flag does not take a value: -" + short_key;
}
return false;
}
flag_values_[sk->second] = true;
provided_[sk->second] = true;
} else if (m->second.type == OptionType::kString) {
if (short_eq == std::string::npos) {
if (i + 1 >= argc) {
if (error) {
*error = "Missing value for -" + short_key;
}
return false;
}
short_value = argv[++i];
}
string_values_[sk->second] = short_value;
provided_[sk->second] = true;
} else if (m->second.type == OptionType::kInt) {
long parsed;
if (short_eq == std::string::npos) {
if (i + 1 >= argc) {
if (error) {
*error = "Missing value for -" + short_key;
}
return false;
}
short_value = argv[++i];
}
errno = 0;
char *endp = nullptr;
parsed = std::strtol(short_value.c_str(), &endp, 0);
if (errno != 0 || endp == short_value.c_str() || *endp != '\0' ||
parsed < INT_MIN || parsed > INT_MAX) {
if (error) {
*error = "Invalid integer for -" + short_key + ": " + short_value;
}
return false;
}
int_values_[sk->second] = static_cast<int>(parsed);
provided_[sk->second] = true;
}
continue;
}
if (token.rfind("--", 0) != 0) {
if (error) {
*error = "Unexpected positional argument: " + token;
}
return false;
}
std::string key;
std::string value;
size_t eq = token.find('=');
if (eq == std::string::npos) {
key = token.substr(2);
} else {
key = token.substr(2, eq - 2);
value = token.substr(eq + 1);
}
auto m = meta_.find(key);
if (m == meta_.end()) {
if (error) {
*error = "Unknown option: --" + key;
}
return false;
}
if (m->second.type == OptionType::kFlag) {
if (eq != std::string::npos) {
if (error) {
*error = "Flag does not take a value: --" + key;
}
return false;
}
flag_values_[key] = true;
provided_[key] = true;
continue;
}
if (eq == std::string::npos) {
if (i + 1 >= argc) {
if (error) {
*error = "Missing value for --" + key;
}
return false;
}
value = argv[++i];
}
if (m->second.type == OptionType::kString) {
string_values_[key] = value;
provided_[key] = true;
} else if (m->second.type == OptionType::kInt) {
errno = 0;
char *endp = nullptr;
long parsed = std::strtol(value.c_str(), &endp, 0);
if (errno != 0 || endp == value.c_str() || *endp != '\0' ||
parsed < INT_MIN || parsed > INT_MAX) {
if (error) {
*error = "Invalid integer for --" + key + ": " + value;
}
return false;
}
int_values_[key] = static_cast<int>(parsed);
provided_[key] = true;
}
}
for (const auto &key : order_) {
auto m = meta_.find(key);
if (m != meta_.end() && m->second.required && !has(key)) {
if (error) {
*error = "Missing required option: --" + key;
}
return false;
}
}
return true;
}
bool ArgParser::has(const std::string &name) const {
auto p = provided_.find(name);
return p != provided_.end() && p->second;
}
std::string ArgParser::getString(const std::string &name) const {
auto it = string_values_.find(name);
if (it == string_values_.end()) {
return std::string();
}
return it->second;
}
int ArgParser::getInt(const std::string &name) const {
auto it = int_values_.find(name);
if (it == int_values_.end()) {
return 0;
}
return it->second;
}
bool ArgParser::getFlag(const std::string &name) const {
auto it = flag_values_.find(name);
if (it == flag_values_.end()) {
return false;
}
return it->second;
}
std::string ArgParser::helpText() const {
std::ostringstream oss;
oss << "Usage: " << program_name_ << " [options]\n\n";
oss << "Options:\n";
oss << " -h, --help Show this help\n";
for (const auto &key : order_) {
auto m = meta_.find(key);
if (m == meta_.end()) {
continue;
}
oss << " ";
if (!m->second.short_name.empty()) {
oss << "-" << m->second.short_name << ", ";
} else {
oss << " ";
}
oss << "--" << key;
if (m->second.type != OptionType::kFlag) {
oss << " <value>";
}
if (m->second.required) {
oss << " (required)";
}
oss << "\n";
oss << " " << m->second.help;
if (m->second.type == OptionType::kString) {
auto s = string_values_.find(key);
if (s != string_values_.end()) {
oss << " [default: '" << s->second << "']";
}
} else {
if (m->second.type == OptionType::kInt) {
auto iv = int_values_.find(key);
if (iv != int_values_.end()) {
oss << " [default: " << iv->second << "]";
}
}
}
oss << "\n";
}
return oss.str();
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
class ArgParser {
public:
struct StringOption {
std::string name;
std::string default_value;
std::string help;
bool required;
};
struct IntOption {
std::string name;
int default_value;
std::string help;
bool required;
};
explicit ArgParser(std::string program_name);
void addString(const std::string &name,
const std::string &default_value,
const std::string &help,
bool required = false,
const std::string &short_name = "");
void addInt(const std::string &name,
int default_value,
const std::string &help,
bool required = false,
const std::string &short_name = "");
void addFlag(const std::string &name,
const std::string &help,
const std::string &short_name = "");
bool parse(int argc, char **argv, std::string *error);
bool has(const std::string &name) const;
std::string getString(const std::string &name) const;
int getInt(const std::string &name) const;
bool getFlag(const std::string &name) const;
std::string helpText() const;
private:
enum class OptionType {
kString,
kInt,
kFlag
};
struct OptionMeta {
OptionType type;
std::string help;
bool required;
std::string short_name;
};
std::string program_name_;
std::vector<std::string> order_;
std::unordered_map<std::string, OptionMeta> meta_;
std::unordered_map<std::string, std::string> string_values_;
std::unordered_map<std::string, int> int_values_;
std::unordered_map<std::string, bool> flag_values_;
std::unordered_map<std::string, bool> provided_;
std::unordered_map<std::string, std::string> short_to_long_;
};

View File

@@ -0,0 +1,276 @@
#include "digilent_jtag.hpp"
#include <algorithm>
#include <cstring>
#include <digilent/adept/dmgr.h>
#include <digilent/adept/djtg.h>
namespace {
constexpr int kDefaultIrBits = 6;
std::string ercToString(ERC erc) {
char code[cchErcMax] = {0};
char msg[cchErcMsgMax] = {0};
if (DmgrSzFromErc(erc, code, msg)) {
return std::string(code) + ": " + msg;
}
return "ERC=" + std::to_string(erc);
}
inline uint8_t getBit(const uint8_t* packed_bits, int bit_idx) {
return static_cast<uint8_t>((packed_bits[bit_idx / 8] >> (bit_idx % 8)) & 0x1u);
}
inline void setBit(uint8_t* packed_bits, int bit_idx, uint8_t bit) {
const uint8_t mask = static_cast<uint8_t>(1u << (bit_idx % 8));
if (bit & 0x1u) {
packed_bits[bit_idx / 8] |= mask;
} else {
packed_bits[bit_idx / 8] &= static_cast<uint8_t>(~mask);
}
}
} // namespace
DigilentJtag::DigilentJtag() : hif_(hifInvalid), enabled_port_(-1), last_error_() {}
DigilentJtag::~DigilentJtag() { close(); }
bool DigilentJtag::open(int port) {
close();
int count = 0;
if (!DmgrEnumDevices(&count)) {
return setErrorFromDmgr("DmgrEnumDevices");
}
if (count <= 0) {
return setError("open: no Digilent devices found");
}
DVC dvc{};
if (!DmgrGetDvc(0, &dvc)) {
return setErrorFromDmgr("DmgrGetDvc");
}
return open(std::string(dvc.szConn), port);
}
bool DigilentJtag::open(const std::string& selector, int port) {
close();
if (selector.empty()) {
return setError("open: selector is empty");
}
std::vector<char> sel(selector.begin(), selector.end());
sel.push_back('\0');
if (!DmgrOpen(&hif_, sel.data())) {
hif_ = hifInvalid;
return setErrorFromDmgr("DmgrOpen");
}
if (!DjtgEnableEx(hif_, static_cast<INT32>(port))) {
if (!DjtgEnable(hif_)) {
DmgrClose(hif_);
hif_ = hifInvalid;
return setErrorFromDmgr("DjtgEnableEx/DjtgEnable");
}
enabled_port_ = 0;
} else {
enabled_port_ = port;
}
last_error_.clear();
return true;
}
void DigilentJtag::close() {
if (hif_ != hifInvalid) {
(void)DjtgDisable(hif_);
(void)DmgrClose(hif_);
}
hif_ = hifInvalid;
enabled_port_ = -1;
}
bool DigilentJtag::isOpen() const { return hif_ != hifInvalid; }
HIF DigilentJtag::handle() const { return hif_; }
bool DigilentJtag::setSpeed(uint32_t requested_hz, uint32_t* actual_hz) {
if (!isOpen()) {
return setError("setSpeed: device not open");
}
DWORD actual = 0;
if (!DjtgSetSpeed(hif_, static_cast<DWORD>(requested_hz), &actual)) {
return setErrorFromDmgr("DjtgSetSpeed");
}
if (actual_hz) {
*actual_hz = static_cast<uint32_t>(actual);
}
last_error_.clear();
return true;
}
bool DigilentJtag::setChain(int chain, int ir_bits) {
uint32_t opcode = 0;
if (chain == 1) {
opcode = 0x02; // USER1 on Spartan-6
} else if (chain == 2) {
opcode = 0x03; // USER2 on Spartan-6
} else {
return setError("setChain: unsupported chain index (expected 1 or 2)");
}
return setInstruction(opcode, ir_bits);
}
bool DigilentJtag::setInstruction(uint32_t instruction, int ir_bits) {
if (!isOpen()) {
return setError("setInstruction: device not open");
}
if (ir_bits <= 0 || ir_bits > 64) {
return setError("setInstruction: ir_bits out of range");
}
// Force Test-Logic-Reset, then RTI.
uint8_t tlr = 0x3f; // 6 ones
if (!putTmsBits(&tlr, 6)) return false;
uint8_t rti = 0x00; // one zero
if (!putTmsBits(&rti, 1)) return false;
// RTI -> Shift-IR : 1,1,0,0 (LSB-first 0b0011)
uint8_t to_shift_ir = 0x03;
if (!putTmsBits(&to_shift_ir, 4)) return false;
std::vector<uint8_t> tx(static_cast<size_t>((ir_bits + 7) / 8), 0);
for (int i = 0; i < ir_bits && i < 64; ++i) {
if ((instruction >> i) & 0x1u) {
tx[static_cast<size_t>(i / 8)] |= static_cast<uint8_t>(1u << (i % 8));
}
}
std::vector<uint8_t> rx(tx.size(), 0);
if (ir_bits > 1) {
if (!putTdiBits(false, tx.data(), rx.data(), ir_bits - 1)) return false;
}
const uint8_t last_tx = getBit(tx.data(), ir_bits - 1);
uint8_t last_rx = 0;
if (!putTdiBits(true, &last_tx, &last_rx, 1)) return false;
// Exit1-IR -> Update-IR -> Idle: 1,0
uint8_t to_idle = 0x01;
if (!putTmsBits(&to_idle, 2)) return false;
last_error_.clear();
return true;
}
bool DigilentJtag::shiftData(const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count) {
if (!isOpen()) {
return setError("shiftData: device not open");
}
if (bit_count <= 0) {
return setError("shiftData: bit_count must be > 0");
}
const size_t nbytes = static_cast<size_t>((bit_count + 7) / 8);
std::vector<uint8_t> tx_zeros;
if (!tx_bits) {
tx_zeros.assign(nbytes, 0);
tx_bits = tx_zeros.data();
}
std::vector<uint8_t> rx_tmp;
if (!rx_bits) {
rx_tmp.assign(nbytes, 0);
rx_bits = rx_tmp.data();
} else {
std::memset(rx_bits, 0, nbytes);
}
if (!enterShiftDR()) return false;
if (bit_count > 1) {
if (!putTdiBits(false, tx_bits, rx_bits, bit_count - 1)) return false;
}
const uint8_t tx_last = getBit(tx_bits, bit_count - 1);
uint8_t rx_last = 0;
if (!putTdiBits(true, &tx_last, &rx_last, 1)) return false;
setBit(rx_bits, bit_count - 1, rx_last & 0x1u);
if (!leaveShiftToIdle()) return false;
last_error_.clear();
return true;
}
bool DigilentJtag::shiftData(const std::vector<uint8_t>& tx_bits, std::vector<uint8_t>* rx_bits, int bit_count) {
if (bit_count <= 0) {
return setError("shiftData(vector): bit_count must be > 0");
}
const size_t nbytes = static_cast<size_t>((bit_count + 7) / 8);
if (tx_bits.size() < nbytes) {
return setError("shiftData(vector): tx_bits is smaller than required bit_count");
}
std::vector<uint8_t> local_rx;
local_rx.assign(nbytes, 0);
if (!shiftData(tx_bits.data(), local_rx.data(), bit_count)) {
return false;
}
if (rx_bits) {
*rx_bits = std::move(local_rx);
}
return true;
}
const std::string& DigilentJtag::lastError() const { return last_error_; }
bool DigilentJtag::enterShiftDR() {
// Idle -> Select-DR -> Capture-DR -> Shift-DR : 1,0,0
uint8_t to_shift_dr = 0x01;
return putTmsBits(&to_shift_dr, 3);
}
bool DigilentJtag::leaveShiftToIdle() {
// Exit1-DR -> Update-DR -> Idle : 1,0
uint8_t to_idle = 0x01;
return putTmsBits(&to_idle, 2);
}
bool DigilentJtag::putTmsBits(const uint8_t* tms_bits, int bit_count) {
if (!DjtgPutTmsBits(hif_, fFalse, const_cast<uint8_t*>(tms_bits), nullptr, static_cast<DWORD>(bit_count), fFalse)) {
return setErrorFromDmgr("DjtgPutTmsBits");
}
return true;
}
bool DigilentJtag::putTdiBits(bool tms, const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count) {
if (!DjtgPutTdiBits(
hif_,
tms ? fTrue : fFalse,
const_cast<uint8_t*>(tx_bits),
rx_bits,
static_cast<DWORD>(bit_count),
fFalse)) {
return setErrorFromDmgr("DjtgPutTdiBits");
}
return true;
}
bool DigilentJtag::setError(const std::string& msg) {
last_error_ = msg;
return false;
}
bool DigilentJtag::setErrorFromDmgr(const std::string& where) {
last_error_ = where + " failed: " + ercToString(DmgrGetLastError());
return false;
}

View File

@@ -0,0 +1,52 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <digilent/adept/dpcdecl.h>
class DigilentJtag {
public:
DigilentJtag();
~DigilentJtag();
DigilentJtag(const DigilentJtag&) = delete;
DigilentJtag& operator=(const DigilentJtag&) = delete;
bool open(int port = 0);
bool open(const std::string& selector, int port = 0);
void close();
bool isOpen() const;
HIF handle() const;
bool setSpeed(uint32_t requested_hz, uint32_t* actual_hz = nullptr);
// For Spartan-6 style USER chains:
// chain=1 -> USER1 opcode 0x02, chain=2 -> USER2 opcode 0x03
bool setChain(int chain, int ir_bits = 6);
bool setInstruction(uint32_t instruction, int ir_bits);
// Shifts one DR transaction from Idle -> ShiftDR -> UpdateDR -> Idle.
// tx_bits is LSB-first in packed byte form.
// rx_bits, when non-null, receives captured TDO bits in the same packing.
bool shiftData(const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count);
bool shiftData(const std::vector<uint8_t>& tx_bits, std::vector<uint8_t>* rx_bits, int bit_count);
const std::string& lastError() const;
private:
bool enterShiftDR();
bool leaveShiftToIdle();
bool putTmsBits(const uint8_t* tms_bits, int bit_count);
bool putTdiBits(bool tms, const uint8_t* tx_bits, uint8_t* rx_bits, int bit_count);
bool setError(const std::string& msg);
bool setErrorFromDmgr(const std::string& where);
HIF hif_;
int enabled_port_;
std::string last_error_;
};

View File

@@ -0,0 +1,142 @@
import ctypes
import os
from pathlib import Path
def _load_library(path=None):
if path is None:
env_path = os.environ.get("JTAG_BRIDGE_LIB")
if env_path:
path = env_path
else:
path = Path(__file__).with_name("libjtag_wb_bridge.so")
return ctypes.CDLL(str(path))
class JtagBridgeError(RuntimeError):
pass
class JtagBridge:
def __init__(self, library_path=None):
self._handle = None
self._lib = _load_library(library_path)
self._configure()
self._handle = self._lib.jtag_bridge_create()
if not self._handle:
raise JtagBridgeError("failed to allocate bridge handle")
def _configure(self):
self._lib.jtag_bridge_create.restype = ctypes.c_void_p
self._lib.jtag_bridge_destroy.argtypes = [ctypes.c_void_p]
self._lib.jtag_bridge_open.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
self._lib.jtag_bridge_open.restype = ctypes.c_int
self._lib.jtag_bridge_open_selector.argtypes = [
ctypes.c_void_p,
ctypes.c_char_p,
ctypes.c_int,
ctypes.c_int,
]
self._lib.jtag_bridge_open_selector.restype = ctypes.c_int
self._lib.jtag_bridge_close.argtypes = [ctypes.c_void_p]
self._lib.jtag_bridge_set_speed.argtypes = [
ctypes.c_void_p,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint32),
]
self._lib.jtag_bridge_set_speed.restype = ctypes.c_int
self._lib.jtag_bridge_set_chain.argtypes = [ctypes.c_void_p, ctypes.c_int]
self._lib.jtag_bridge_set_chain.restype = ctypes.c_int
self._lib.jtag_bridge_clear_flags.argtypes = [ctypes.c_void_p]
self._lib.jtag_bridge_clear_flags.restype = ctypes.c_int
self._lib.jtag_bridge_ping.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint8)]
self._lib.jtag_bridge_ping.restype = ctypes.c_int
self._lib.jtag_bridge_set_reset.argtypes = [ctypes.c_void_p, ctypes.c_int]
self._lib.jtag_bridge_set_reset.restype = ctypes.c_int
self._lib.jtag_bridge_write8.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint8]
self._lib.jtag_bridge_write8.restype = ctypes.c_int
self._lib.jtag_bridge_read8.argtypes = [
ctypes.c_void_p,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint8),
]
self._lib.jtag_bridge_read8.restype = ctypes.c_int
self._lib.jtag_bridge_write32.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32]
self._lib.jtag_bridge_write32.restype = ctypes.c_int
self._lib.jtag_bridge_read32.argtypes = [
ctypes.c_void_p,
ctypes.c_uint32,
ctypes.POINTER(ctypes.c_uint32),
]
self._lib.jtag_bridge_read32.restype = ctypes.c_int
self._lib.jtag_bridge_last_error.argtypes = [ctypes.c_void_p]
self._lib.jtag_bridge_last_error.restype = ctypes.c_char_p
def close(self):
if self._handle:
self._lib.jtag_bridge_close(self._handle)
def destroy(self):
if self._handle:
self._lib.jtag_bridge_destroy(self._handle)
self._handle = None
def __del__(self):
self.destroy()
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
self.destroy()
return False
def _check(self, ok):
if not ok:
message = self._lib.jtag_bridge_last_error(self._handle)
raise JtagBridgeError(message.decode("utf-8"))
def open(self, port=0, chain=1):
self._check(self._lib.jtag_bridge_open(self._handle, port, chain))
def open_selector(self, selector, port=0, chain=1):
self._check(
self._lib.jtag_bridge_open_selector(
self._handle, selector.encode("utf-8"), port, chain
)
)
def set_speed(self, requested_hz):
actual = ctypes.c_uint32()
self._check(self._lib.jtag_bridge_set_speed(self._handle, requested_hz, ctypes.byref(actual)))
return actual.value
def set_chain(self, chain):
self._check(self._lib.jtag_bridge_set_chain(self._handle, chain))
def clear_flags(self):
self._check(self._lib.jtag_bridge_clear_flags(self._handle))
def ping(self):
value = ctypes.c_uint8()
self._check(self._lib.jtag_bridge_ping(self._handle, ctypes.byref(value)))
return value.value
def set_reset(self, enabled):
self._check(self._lib.jtag_bridge_set_reset(self._handle, int(bool(enabled))))
def write8(self, addr, value):
self._check(self._lib.jtag_bridge_write8(self._handle, addr, value))
def read8(self, addr):
value = ctypes.c_uint8()
self._check(self._lib.jtag_bridge_read8(self._handle, addr, ctypes.byref(value)))
return value.value
def write32(self, addr, value):
self._check(self._lib.jtag_bridge_write32(self._handle, addr, value))
def read32(self, addr):
value = ctypes.c_uint32()
self._check(self._lib.jtag_bridge_read32(self._handle, addr, ctypes.byref(value)))
return value.value

View File

@@ -0,0 +1,113 @@
#include "jtag_wb_bridge_c.h"
#include "jtag_wb_bridge_client.hpp"
#include <new>
struct JtagBridgeHandle {
JtagWishboneBridge bridge;
};
namespace {
template <typename Fn>
int callBridge(JtagBridgeHandle* handle, Fn&& fn) {
if (!handle) {
return 0;
}
return fn(handle->bridge) ? 1 : 0;
}
} // namespace
extern "C" {
JtagBridgeHandle* jtag_bridge_create(void) {
return new (std::nothrow) JtagBridgeHandle();
}
void jtag_bridge_destroy(JtagBridgeHandle* handle) {
delete handle;
}
int jtag_bridge_open(JtagBridgeHandle* handle, int port, int chain) {
return callBridge(handle, [port, chain](JtagWishboneBridge& bridge) {
return bridge.open(port, chain);
});
}
int jtag_bridge_open_selector(JtagBridgeHandle* handle, const char* selector, int port, int chain) {
if (!handle || !selector) {
return 0;
}
return handle->bridge.open(selector, port, chain) ? 1 : 0;
}
void jtag_bridge_close(JtagBridgeHandle* handle) {
if (handle) {
handle->bridge.close();
}
}
int jtag_bridge_set_speed(JtagBridgeHandle* handle, uint32_t requested_hz, uint32_t* actual_hz) {
return callBridge(handle, [requested_hz, actual_hz](JtagWishboneBridge& bridge) {
return bridge.setSpeed(requested_hz, actual_hz);
});
}
int jtag_bridge_set_chain(JtagBridgeHandle* handle, int chain) {
return callBridge(handle, [chain](JtagWishboneBridge& bridge) {
return bridge.setChain(chain);
});
}
int jtag_bridge_clear_flags(JtagBridgeHandle* handle) {
return callBridge(handle, [](JtagWishboneBridge& bridge) {
return bridge.clearFlags();
});
}
int jtag_bridge_ping(JtagBridgeHandle* handle, uint8_t* ping_value) {
return callBridge(handle, [ping_value](JtagWishboneBridge& bridge) {
return bridge.ping(ping_value);
});
}
int jtag_bridge_set_reset(JtagBridgeHandle* handle, int enabled) {
return callBridge(handle, [enabled](JtagWishboneBridge& bridge) {
return bridge.setReset(enabled != 0);
});
}
int jtag_bridge_write8(JtagBridgeHandle* handle, uint32_t addr, uint8_t value) {
return callBridge(handle, [addr, value](JtagWishboneBridge& bridge) {
return bridge.write8(addr, value);
});
}
int jtag_bridge_read8(JtagBridgeHandle* handle, uint32_t addr, uint8_t* value) {
return callBridge(handle, [addr, value](JtagWishboneBridge& bridge) {
return bridge.read8(addr, value);
});
}
int jtag_bridge_write32(JtagBridgeHandle* handle, uint32_t addr, uint32_t value) {
return callBridge(handle, [addr, value](JtagWishboneBridge& bridge) {
return bridge.write32(addr, value);
});
}
int jtag_bridge_read32(JtagBridgeHandle* handle, uint32_t addr, uint32_t* value) {
return callBridge(handle, [addr, value](JtagWishboneBridge& bridge) {
return bridge.read32(addr, value);
});
}
const char* jtag_bridge_last_error(const JtagBridgeHandle* handle) {
if (!handle) {
return "invalid bridge handle";
}
return handle->bridge.lastError().c_str();
}
} // extern "C"

View File

@@ -0,0 +1,34 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct JtagBridgeHandle JtagBridgeHandle;
JtagBridgeHandle* jtag_bridge_create(void);
void jtag_bridge_destroy(JtagBridgeHandle* handle);
int jtag_bridge_open(JtagBridgeHandle* handle, int port, int chain);
int jtag_bridge_open_selector(JtagBridgeHandle* handle, const char* selector, int port, int chain);
void jtag_bridge_close(JtagBridgeHandle* handle);
int jtag_bridge_set_speed(JtagBridgeHandle* handle, uint32_t requested_hz, uint32_t* actual_hz);
int jtag_bridge_set_chain(JtagBridgeHandle* handle, int chain);
int jtag_bridge_clear_flags(JtagBridgeHandle* handle);
int jtag_bridge_ping(JtagBridgeHandle* handle, uint8_t* ping_value);
int jtag_bridge_set_reset(JtagBridgeHandle* handle, int enabled);
int jtag_bridge_write8(JtagBridgeHandle* handle, uint32_t addr, uint8_t value);
int jtag_bridge_read8(JtagBridgeHandle* handle, uint32_t addr, uint8_t* value);
int jtag_bridge_write32(JtagBridgeHandle* handle, uint32_t addr, uint32_t value);
int jtag_bridge_read32(JtagBridgeHandle* handle, uint32_t addr, uint32_t* value);
const char* jtag_bridge_last_error(const JtagBridgeHandle* handle);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,181 @@
#include "jtag_wb_bridge_client.hpp"
#include <cstdio>
namespace {
constexpr uint8_t kOpNop = 0x00;
constexpr uint8_t kOpResetOn = 0x10;
constexpr uint8_t kOpResetOff = 0x11;
constexpr uint8_t kOpWrite8 = 0x20;
constexpr uint8_t kOpRead8 = 0x21;
constexpr uint8_t kOpWrite32 = 0x22;
constexpr uint8_t kOpRead32 = 0x23;
constexpr uint8_t kOpPing = 0x30;
constexpr uint8_t kOpClearFlags = 0x40;
constexpr int kPacketBytes = 9;
constexpr int kPacketBits = 72;
constexpr int kMaxPollAttempts = 32;
void makeCommand(uint8_t out[kPacketBytes], uint8_t opcode, uint32_t addr, uint32_t data) {
out[0] = static_cast<uint8_t>(data);
out[1] = static_cast<uint8_t>(data >> 8);
out[2] = static_cast<uint8_t>(data >> 16);
out[3] = static_cast<uint8_t>(data >> 24);
out[4] = static_cast<uint8_t>(addr);
out[5] = static_cast<uint8_t>(addr >> 8);
out[6] = static_cast<uint8_t>(addr >> 16);
out[7] = static_cast<uint8_t>(addr >> 24);
out[8] = opcode;
}
uint32_t getResponseData32(const uint8_t rx[kPacketBytes]) {
return static_cast<uint32_t>(rx[2]) |
(static_cast<uint32_t>(rx[3]) << 8) |
(static_cast<uint32_t>(rx[4]) << 16) |
(static_cast<uint32_t>(rx[5]) << 24);
}
uint8_t getLastOpcode(const uint8_t rx[kPacketBytes]) {
return rx[0];
}
} // namespace
bool JtagWishboneBridge::open(int port, int chain) {
if (!jtag_.open(port)) {
return setError(jtag_.lastError());
}
if (!jtag_.setChain(chain)) {
const std::string msg = jtag_.lastError();
jtag_.close();
return setError(msg);
}
last_error_.clear();
return true;
}
bool JtagWishboneBridge::open(const std::string& selector, int port, int chain) {
if (!jtag_.open(selector, port)) {
return setError(jtag_.lastError());
}
if (!jtag_.setChain(chain)) {
const std::string msg = jtag_.lastError();
jtag_.close();
return setError(msg);
}
last_error_.clear();
return true;
}
void JtagWishboneBridge::close() {
jtag_.close();
last_error_.clear();
}
bool JtagWishboneBridge::isOpen() const {
return jtag_.isOpen();
}
bool JtagWishboneBridge::setSpeed(uint32_t requested_hz, uint32_t* actual_hz) {
if (!jtag_.setSpeed(requested_hz, actual_hz)) {
return setError(jtag_.lastError());
}
last_error_.clear();
return true;
}
bool JtagWishboneBridge::setChain(int chain) {
if (!jtag_.setChain(chain)) {
return setError(jtag_.lastError());
}
last_error_.clear();
return true;
}
bool JtagWishboneBridge::clearFlags() {
return executeCommand(kOpClearFlags, 0, 0, nullptr);
}
bool JtagWishboneBridge::ping(uint8_t* ping_value) {
uint32_t response = 0;
if (!executeCommand(kOpPing, 0, 0, &response)) {
return false;
}
if (ping_value) {
*ping_value = static_cast<uint8_t>(response & 0xffu);
}
return true;
}
bool JtagWishboneBridge::setReset(bool enabled) {
return executeCommand(enabled ? kOpResetOn : kOpResetOff, 0, 0, nullptr);
}
bool JtagWishboneBridge::write8(uint32_t addr, uint8_t value) {
return executeCommand(kOpWrite8, addr, value, nullptr);
}
bool JtagWishboneBridge::read8(uint32_t addr, uint8_t* value) {
uint32_t response = 0;
if (!value) {
return setError("read8: value pointer is null");
}
if (!executeCommand(kOpRead8, addr, 0, &response)) {
return false;
}
*value = static_cast<uint8_t>(response & 0xffu);
return true;
}
bool JtagWishboneBridge::write32(uint32_t addr, uint32_t value) {
return executeCommand(kOpWrite32, addr, value, nullptr);
}
bool JtagWishboneBridge::read32(uint32_t addr, uint32_t* value) {
if (!value) {
return setError("read32: value pointer is null");
}
return executeCommand(kOpRead32, addr, 0, value);
}
const std::string& JtagWishboneBridge::lastError() const {
return last_error_;
}
bool JtagWishboneBridge::executeCommand(uint8_t opcode, uint32_t addr, uint32_t data, uint32_t* response_data) {
if (!jtag_.isOpen()) {
return setError("executeCommand: device not open");
}
uint8_t tx[kPacketBytes] = {0};
uint8_t rx[kPacketBytes] = {0};
makeCommand(tx, opcode, addr, data);
if (!jtag_.shiftData(tx, rx, kPacketBits)) {
return setError(jtag_.lastError());
}
for (int i = 0; i < kMaxPollAttempts; ++i) {
makeCommand(tx, kOpNop, 0, 0);
if (!jtag_.shiftData(tx, rx, kPacketBits)) {
return setError(jtag_.lastError());
}
if (getLastOpcode(rx) == opcode) {
if (response_data) {
*response_data = getResponseData32(rx);
}
last_error_.clear();
return true;
}
}
char msg[96];
std::snprintf(msg, sizeof(msg), "command 0x%02x timed out after %d polls", opcode, kMaxPollAttempts);
return setError(msg);
}
bool JtagWishboneBridge::setError(const std::string& msg) {
last_error_ = msg;
return false;
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include "digilent_jtag.hpp"
#include <cstdint>
#include <string>
class JtagWishboneBridge {
public:
JtagWishboneBridge() = default;
~JtagWishboneBridge() = default;
JtagWishboneBridge(const JtagWishboneBridge&) = delete;
JtagWishboneBridge& operator=(const JtagWishboneBridge&) = delete;
bool open(int port = 0, int chain = 1);
bool open(const std::string& selector, int port = 0, int chain = 1);
void close();
bool isOpen() const;
bool setSpeed(uint32_t requested_hz, uint32_t* actual_hz = nullptr);
bool setChain(int chain);
bool clearFlags();
bool ping(uint8_t* ping_value = nullptr);
bool setReset(bool enabled);
bool write8(uint32_t addr, uint8_t value);
bool read8(uint32_t addr, uint8_t* value);
bool write32(uint32_t addr, uint32_t value);
bool read32(uint32_t addr, uint32_t* value);
const std::string& lastError() const;
private:
bool executeCommand(uint8_t opcode, uint32_t addr, uint32_t data, uint32_t* response_data);
bool setError(const std::string& msg);
DigilentJtag jtag_;
std::string last_error_;
};

View File

@@ -0,0 +1,104 @@
#include "argparse.hpp"
#include "jtag_wb_bridge_client.hpp"
#include <cstdio>
#include <string>
int main(int argc, char** argv) {
ArgParser parser(argc > 0 ? argv[0] : "test");
parser.addString("file", "", "File to write", true, "f");
parser.addFlag("verify", "Verify", "v");
std::string parse_error;
if (!parser.parse(argc, argv, &parse_error)) {
if (parse_error == "help") {
std::printf("%s", parser.helpText().c_str());
return 0;
}
std::printf("Argument error: %s\n\n", parse_error.c_str());
std::printf("%s", parser.helpText().c_str());
return -1;
}
JtagWishboneBridge bridge;
if (!bridge.open()) {
std::printf("Could not open programmer: %s\n", bridge.lastError().c_str());
return -1;
}
if (!bridge.clearFlags()) {
std::printf("Could not clear flags: %s\n", bridge.lastError().c_str());
return -1;
}
uint8_t ping_value = 0;
if (!bridge.ping(&ping_value)) {
std::printf("PING command failed: %s\n", bridge.lastError().c_str());
return -1;
}
if (ping_value != 0xa5u) {
std::printf("PING response was not right: %02x\n", ping_value);
return -1;
}
const std::string file = parser.getString("file");
FILE* f = std::fopen(file.c_str(), "rb");
if (!f) {
std::printf("Could not open file\n");
return -1;
}
if (!bridge.setReset(true)) {
std::printf("Could not assert reset: %s\n", bridge.lastError().c_str());
std::fclose(f);
return -1;
}
int nr = 0;
uint32_t addr = 0;
do {
uint32_t buf[32];
nr = static_cast<int>(std::fread(buf, sizeof(uint32_t), 32, f));
for (int i = 0; i < nr; ++i) {
if (!bridge.write32(addr + static_cast<uint32_t>(i * 4), buf[i])) {
std::printf("Write failed at %04x: %s\n",
addr + static_cast<uint32_t>(i * 4),
bridge.lastError().c_str());
std::fclose(f);
return -1;
}
std::printf(".");
}
std::printf("\n");
if (parser.getFlag("verify")) {
for (int i = 0; i < nr; ++i) {
uint32_t value = 0;
if (!bridge.read32(addr + static_cast<uint32_t>(i * 4), &value)) {
std::printf("Read failed at %04x: %s\n",
addr + static_cast<uint32_t>(i * 4),
bridge.lastError().c_str());
std::fclose(f);
return -1;
}
if (value != buf[i]) {
std::printf(" -- Verify failed at %04x : %08x != %08x\n",
addr + static_cast<uint32_t>(i * 4),
value,
buf[i]);
}
}
}
addr += static_cast<uint32_t>(nr * 4);
} while (nr > 0);
if (!bridge.setReset(false)) {
std::printf("Could not deassert reset: %s\n", bridge.lastError().c_str());
std::fclose(f);
return -1;
}
std::fclose(f);
return 0;
}

View File

@@ -0,0 +1,8 @@
from libjtag_wb_bridge.jtag_bridge import JtagBridge
with JtagBridge() as bridge:
bridge.open(port=0, chain=1)
bridge.clear_flags()
assert bridge.ping() == 0xA5
bridge.write32(0x0, 0xAA)

View File

@@ -0,0 +1,69 @@
`timescale 1ns/1ps
module formal_wb_gpio #(
parameter [31:0] address = 32'h00000000
);
(* gclk *) reg i_wb_clk;
(* anyseq *) reg i_rst;
(* anyseq *) reg i_wb_rst;
(* anyseq *) reg [31:0] i_wb_adr;
(* anyseq *) reg [31:0] i_wb_dat;
(* anyseq *) reg [3:0] i_wb_sel;
(* anyseq *) reg i_wb_we;
(* anyseq *) reg i_wb_stb;
(* anyseq *) reg [31:0] i_gpio;
wire [31:0] o_wb_rdt;
wire o_wb_ack;
wire [31:0] o_gpio;
wire i_wb_cyc;
reg f_past_valid;
assign i_wb_cyc = i_wb_stb || o_wb_ack;
wb_gpio #(
.address(address)
) dut (
.i_wb_clk(i_wb_clk),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_gpio(i_gpio),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack),
.o_gpio(o_gpio)
);
formal_wb_slave_checker wb_checker (
.i_clk(i_wb_clk),
.i_rst(i_rst),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack)
);
initial f_past_valid = 1'b0;
always @(posedge i_wb_clk) begin
f_past_valid <= 1'b1;
// R1: reads return the sampled GPIO input on the following cycle
if (f_past_valid && !$past(i_wb_rst) && !i_wb_rst && $past(i_wb_stb) && !$past(i_wb_we)) begin
assert(o_wb_rdt == $past(i_gpio));
end
// R2: reset clears the output register and read data register
if (f_past_valid && $past(i_wb_rst)) begin
assert(o_gpio == 32'h00000000);
assert(o_wb_rdt == 32'h00000000);
end
end
endmodule

View File

@@ -0,0 +1,13 @@
[options]
mode prove
depth 8
[engines]
smtbmc z3 parallel.enable=true parallel.threads.max=8
[script]
{{"-formal"|gen_reads}}
prep -top {{top_level}}
[files]
{{files}}

View File

@@ -0,0 +1,13 @@
[options]
mode prove
depth 8
[engines]
smtbmc z3 parallel.enable=true parallel.threads.max=8
[script]
{{"-formal"|gen_reads}}
prep -top {{top_level}}
[files]
{{files}}

View File

@@ -0,0 +1,2 @@
SBY 17:32:33 [cores/wb/wb_gpio/formal/wb_gpio] Removing directory '/data/joppe/projects/fusesoc_test/cores/wb/wb_gpio/formal/wb_gpio'.
SBY 17:32:33 [cores/wb/wb_gpio/formal/wb_gpio] Copy '/data/joppe/projects/fusesoc_test/{{files}}' to '/data/joppe/projects/fusesoc_test/cores/wb/wb_gpio/formal/wb_gpio/src/{{files}}'.

View File

@@ -0,0 +1,56 @@
module wb_gpio #(
parameter address = 32'h00000000
)(
input wire i_wb_clk,
input wire i_wb_rst, // optional; tie low if unused
input wire [31:0] i_wb_adr, // optional; can ignore for single-reg
input wire [31:0] i_wb_dat,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_stb,
input wire [31:0] i_gpio,
output reg [31:0] o_wb_rdt,
output reg o_wb_ack,
output reg [31:0] o_gpio
);
initial o_gpio <= 32'h00000000;
initial o_wb_rdt <= 32'h00000000;
wire addr_check;
assign addr_check = (i_wb_adr == address);
// One-cycle ACK pulse per request (works even if stb stays high)
initial o_wb_ack <= 1'b0;
always @(posedge i_wb_clk) begin
if (i_wb_rst) begin
o_wb_ack <= 1'b0;
end else begin
o_wb_ack <= i_wb_stb & ~o_wb_ack; // pulse while stb asserted
end
end
// Read data (combinational or registered; registered here)
always @(posedge i_wb_clk) begin
if (i_wb_rst) begin
o_wb_rdt <= 32'h0;
end else if (i_wb_stb && !i_wb_we) begin
o_wb_rdt <= i_gpio;
end
end
// Write latch (update on the acknowledged cycle)
always @(posedge i_wb_clk) begin
if (i_wb_rst) begin
o_gpio <= 32'h0;
end else if (i_wb_stb && i_wb_we && addr_check && (i_wb_stb & ~o_wb_ack)) begin
// Apply byte enables (so sb works if the master uses sel)
if (i_wb_sel[0]) o_gpio[7:0] <= i_wb_dat[7:0];
if (i_wb_sel[1]) o_gpio[15:8] <= i_wb_dat[15:8];
if (i_wb_sel[2]) o_gpio[23:16] <= i_wb_dat[23:16];
if (i_wb_sel[3]) o_gpio[31:24] <= i_wb_dat[31:24];
end
end
endmodule

View File

@@ -0,0 +1,43 @@
CAPI=2:
name: joppeb:wb:wb_gpio:1.0
description: Wishbone GPIO peripheral
filesets:
rtl:
files:
- rtl/wb_gpio.v
file_type: verilogSource
formal_rtl:
depend:
- joppeb:wb:formal_checker
files:
- formal/formal_wb_gpio.v
file_type: verilogSource
formal_cfg:
files:
- formal/wb_gpio.sby
file_type: sbyConfigTemplate
targets:
default:
filesets:
- rtl
toplevel: wb_gpio
parameters:
- address
formal:
default_tool: symbiyosys
filesets:
- rtl
- formal_rtl
- formal_cfg
toplevel: formal_wb_gpio
parameters:
- address
parameters:
address:
datatype: int
description: Wishbone address matched by this peripheral
paramtype: vlogparam

View File

@@ -0,0 +1,46 @@
`timescale 1ns/1ps
module formal_wb_mem32;
(* gclk *) reg i_clk;
(* anyseq *) reg i_rst;
(* anyseq *) reg i_wb_rst;
(* anyseq *) reg [31:0] i_wb_adr;
(* anyseq *) reg [31:0] i_wb_dat;
(* anyseq *) reg [3:0] i_wb_sel;
(* anyseq *) reg i_wb_we;
(* anyseq *) reg i_wb_stb;
(* anyseq *) reg i_wb_cyc;
wire [31:0] o_wb_rdt;
wire o_wb_ack;
wb_mem32 #(
.memsize(16),
.sim(1)
) dut (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack)
);
formal_wb_slave_checker wb_checker (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_rdt),
.o_wb_ack(o_wb_ack)
);
endmodule

View File

@@ -0,0 +1,15 @@
[options]
mode prove
depth 8
[engines]
smtbmc z3 parallel.enable=true parallel.threads.max=8
[script]
read -formal clog2.vh
{{"-formal"|gen_reads}}
prep -top {{top_level}}
[files]
src/joppeb_util_clog2_1.0/clog2.vh
{{files}}

View File

@@ -0,0 +1,66 @@
`timescale 1ns/1ps
`include "clog2.vh"
module wb_mem32 #(
parameter memfile = "",
parameter memsize = 8192,
parameter sim = 1'b0
)(
input wire i_clk,
input wire i_rst,
input wire i_wb_rst,
input wire [31:0] i_wb_adr,
input wire [31:0] i_wb_dat,
input wire [3:0] i_wb_sel,
input wire i_wb_we,
input wire i_wb_stb,
input wire i_wb_cyc,
output wire [31:0] o_wb_rdt,
output wire o_wb_ack
);
localparam integer mem_depth = memsize/4;
localparam integer mem_aw = (mem_depth <= 1) ? 1 : `CLOG2(mem_depth);
reg [31:0] mem [0:mem_depth-1] /* verilator public */;
reg [31:0] wb_rdt_r;
reg wb_ack_r;
wire [mem_aw-1:0] wb_word_adr = i_wb_adr[mem_aw+1:2];
assign o_wb_rdt = wb_rdt_r;
assign o_wb_ack = wb_ack_r;
always @(posedge i_clk) begin
if (i_rst || i_wb_rst) begin
wb_ack_r <= 1'b0;
wb_rdt_r <= 32'b0;
end else begin
wb_ack_r <= i_wb_stb & i_wb_cyc & ~wb_ack_r;
if (i_wb_stb & i_wb_cyc & ~wb_ack_r) begin
wb_rdt_r <= mem[wb_word_adr];
if (i_wb_we) begin
if (i_wb_sel[0]) mem[wb_word_adr][7:0] <= i_wb_dat[7:0];
if (i_wb_sel[1]) mem[wb_word_adr][15:8] <= i_wb_dat[15:8];
if (i_wb_sel[2]) mem[wb_word_adr][23:16] <= i_wb_dat[23:16];
if (i_wb_sel[3]) mem[wb_word_adr][31:24] <= i_wb_dat[31:24];
end
end
end
end
integer i;
initial begin
if (sim == 1'b1) begin
for (i = 0; i < mem_depth; i = i + 1)
mem[i] = 32'h00000000;
end
if (|memfile) begin
$display("Preloading %m from %s", memfile);
$readmemh(memfile, mem);
end
wb_rdt_r = 32'b0;
wb_ack_r = 1'b0;
end
endmodule

View File

@@ -0,0 +1,178 @@
`timescale 1ns/1ps
module tb_wb_mem32;
reg i_clk;
reg i_rst;
reg i_wb_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_rdt;
wire o_wb_ack;
reg [31:0] read_data;
wb_mem32 #(
.memsize(64),
.sim(1)
) dut (
.i_clk(i_clk),
.i_rst(i_rst),
.i_wb_rst(i_wb_rst),
.i_wb_adr(i_wb_adr),
.i_wb_dat(i_wb_dat),
.i_wb_sel(i_wb_sel),
.i_wb_we(i_wb_we),
.i_wb_stb(i_wb_stb),
.i_wb_cyc(i_wb_cyc),
.o_wb_rdt(o_wb_rdt),
.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;
@(posedge i_clk);
#1;
if (!o_wb_ack) begin
$display("ERROR: write ack missing at time %0t", $time);
$finish;
end
@(posedge i_clk);
i_wb_stb <= 1'b0;
i_wb_cyc <= 1'b0;
i_wb_we <= 1'b0;
i_wb_sel <= 4'b0000;
i_wb_dat <= 32'h0;
@(posedge i_clk);
#1;
if (o_wb_ack) begin
$display("ERROR: write ack did not clear at time %0t", $time);
$finish;
end
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'h0;
i_wb_sel <= 4'b1111;
i_wb_we <= 1'b0;
i_wb_stb <= 1'b1;
i_wb_cyc <= 1'b1;
@(posedge i_clk);
#1;
if (!o_wb_ack) begin
$display("ERROR: read ack missing at time %0t", $time);
$finish;
end
data = o_wb_rdt;
@(posedge i_clk);
i_wb_stb <= 1'b0;
i_wb_cyc <= 1'b0;
i_wb_sel <= 4'b0000;
@(posedge i_clk);
#1;
if (o_wb_ack) begin
$display("ERROR: read ack did not clear at time %0t", $time);
$finish;
end
end
endtask
initial begin
$dumpfile("wb_mem32.vcd");
$dumpvars(0, tb_wb_mem32);
i_rst = 1'b1;
i_wb_rst = 1'b0;
i_wb_adr = 32'h0;
i_wb_dat = 32'h0;
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;
@(negedge i_clk);
i_wb_adr <= 32'h0000_0000;
i_wb_sel <= 4'b1111;
i_wb_stb <= 1'b1;
i_wb_cyc <= 1'b0;
@(posedge i_clk);
#1;
if (o_wb_ack) begin
$display("ERROR: ack asserted without cyc at time %0t", $time);
$finish;
end
@(negedge i_clk);
i_wb_stb <= 1'b0;
i_wb_sel <= 4'b0000;
wb_read(32'h0000_0000, read_data);
if (read_data !== 32'h0000_0000) begin
$display("ERROR: reset contents mismatch, got %08x", read_data);
$finish;
end
wb_write(32'h0000_0000, 32'hA1B2_C3D4, 4'b1111);
wb_read(32'h0000_0000, read_data);
if (read_data !== 32'hA1B2_C3D4) begin
$display("ERROR: full-word write mismatch, got %08x", read_data);
$finish;
end
wb_write(32'h0000_0000, 32'h5566_7788, 4'b0101);
wb_read(32'h0000_0000, read_data);
if (read_data !== 32'hA166_C388) begin
$display("ERROR: byte-enable write mismatch, got %08x", read_data);
$finish;
end
wb_write(32'h0000_0004, 32'hDEAD_BEEF, 4'b1111);
wb_read(32'h0000_0004, read_data);
if (read_data !== 32'hDEAD_BEEF) begin
$display("ERROR: second word mismatch, got %08x", read_data);
$finish;
end
wb_read(32'h0000_0000, read_data);
if (read_data !== 32'hA166_C388) begin
$display("ERROR: first word changed unexpectedly, got %08x", read_data);
$finish;
end
$display("PASS: wb_mem32 testbench completed successfully");
$finish;
end
endmodule

View File

@@ -0,0 +1,63 @@
CAPI=2:
name: joppeb:wb:wb_mem32:1.0
description: Wishbone classic block ram
filesets:
rtl:
depend:
- joppeb:util:clog2
files:
- rtl/wb_mem32.v
file_type: verilogSource
tb:
files:
- tb/tb_wb_mem32.v
file_type: verilogSource
formal_rtl:
depend:
- joppeb:wb:formal_checker
files:
- formal/formal_wb_mem32.v
file_type: verilogSource
formal_cfg:
files:
- formal/wb_mem32.sby
file_type: sbyConfigTemplate
targets:
default:
filesets:
- rtl
toplevel: wb_mem32
parameters:
- memfile
- memsize
- sim
sim:
default_tool: icarus
filesets:
- rtl
- tb
toplevel: tb_wb_mem32
formal:
default_tool: symbiyosys
filesets:
- rtl
- formal_rtl
- formal_cfg
toplevel: formal_wb_mem32
parameters:
memfile:
datatype: str
description: Data to fill the mem
paramtype: vlogparam
memsize:
datatype: int
description: Size of memory in bytes, should be a multiple of 32bit
paramtype: vlogparam
sim:
datatype: int
description: Simulation version, fills rest memory with 0
paramtype: vlogparam