From fa641b1eabf3ed133e37d3f7c7ec2d86a5962d7e Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sat, 28 Feb 2026 13:52:08 +0100 Subject: [PATCH 01/12] Initial commit Added nco_q15 --- .gitignore | 1 + cores/nco_q15/nco_q15.core | 22 +++++ cores/nco_q15/rtl/nco_q15.v | 163 ++++++++++++++++++++++++++++++++++ cores/nco_q15/tb/tb_nco_q15.v | 43 +++++++++ fusesoc.conf | 6 ++ 5 files changed, 235 insertions(+) create mode 100644 .gitignore create mode 100644 cores/nco_q15/nco_q15.core create mode 100644 cores/nco_q15/rtl/nco_q15.v create mode 100644 cores/nco_q15/tb/tb_nco_q15.v create mode 100644 fusesoc.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/cores/nco_q15/nco_q15.core b/cores/nco_q15/nco_q15.core new file mode 100644 index 0000000..6a47f5b --- /dev/null +++ b/cores/nco_q15/nco_q15.core @@ -0,0 +1,22 @@ +CAPI=2: + +name: joppeb:base:nco_q15:1.0 + +filesets: + rtl: + files: + - rtl/nco_q15.v + file_type: verilogSource + + tb: + files: + - tb/tb_nco_q15.v + file_type: verilogSource + +targets: + sim: + default_tool: icarus + filesets: + - rtl + - tb + toplevel: tb_nco_q15 \ No newline at end of file diff --git a/cores/nco_q15/rtl/nco_q15.v b/cores/nco_q15/rtl/nco_q15.v new file mode 100644 index 0000000..fe20395 --- /dev/null +++ b/cores/nco_q15/rtl/nco_q15.v @@ -0,0 +1,163 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Small number controlled oscillator +// Generates a sine and cosine and uses no multiplications, just some logic and +// a 64-entry LUT. It outputs Q15 data (but the LUT is 8 bits wide) +// params: +// -- CLK_HZ : input clock frequency in Hz +// -- FS_HZ : output sample frequency in Hz +// inout: +// -- clk : input clock +// -- rst_n : reset +// -- freq_hz : decimal number of desired generated frequency in Hz, 0-FS/2 +// -- sin_q15/cos_q15 : I and Q outputs +// -- clk_en : output valid strobe +// ============================================================================= +module nco_q15 #( + parameter integer CLK_HZ = 120_000_000, // input clock + parameter integer FS_HZ = 40_000 // sample rate +)( + input wire clk, // CLK_HZ domain + input wire rst_n, // async active-low reset + input wire [31:0] freq_hz, // desired output frequency (Hz), 0..FS_HZ/2 + + output reg signed [15:0] sin_q15, // Q1.15 sine + output reg signed [15:0] cos_q15, // Q1.15 cosine + output reg clk_en // 1-cycle strobe @ FS_HZ +); + localparam integer PHASE_FRAC_BITS = 6; + localparam integer QTR_ADDR_BITS = 6; + localparam integer PHASE_BITS = 2 + QTR_ADDR_BITS + PHASE_FRAC_BITS; + localparam integer DIV = CLK_HZ / FS_HZ; + localparam integer SHIFT = 32; + + // Fixed-point reciprocal (constant): RECIP = round( (2^PHASE_BITS * 2^SHIFT) / FS_HZ ) + localparam [63:0] RECIP = ( ((64'd1 << PHASE_BITS) << SHIFT) + (FS_HZ/2) ) / FS_HZ; + + // Sample-rate tick + function integer clog2; + input integer v; integer r; begin r=0; v=v-1; while (v>0) begin v=v>>1; r=r+1; end clog2=r; end + endfunction + + reg [clog2(DIV)-1:0] tick_cnt; + always @(posedge clk or negedge rst_n) begin + if (!rst_n) begin tick_cnt <= 0; clk_en <= 1'b0; end + else begin + clk_en <= 1'b0; + if (tick_cnt == DIV-1) begin tick_cnt <= 0; clk_en <= 1'b1; end + else tick_cnt <= tick_cnt + 1'b1; + end + end + + // 32-cycle shift-add multiply: prod = freq_hz * RECIP (no multiplications themself) + // Starts at clk_en, finishes in 32 cycles (<< available cycles per sample). + reg mul_busy; + reg [5:0] mul_i; // 0..31 + reg [31:0] f_reg; + reg [95:0] acc; // accumulator for product (32x64 -> 96b) + + wire [95:0] recip_shift = {{32{1'b0}}, RECIP} << mul_i; // shift constant by i + + always @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + mul_busy <= 1'b0; mul_i <= 6'd0; f_reg <= 32'd0; acc <= 96'd0; + end else begin + if (clk_en && !mul_busy) begin + // kick off a new multiply this sample + mul_busy <= 1'b1; + mul_i <= 6'd0; + f_reg <= (freq_hz > (FS_HZ>>1)) ? (FS_HZ>>1) : freq_hz; // clamp to Nyquist + acc <= 96'd0; + end else if (mul_busy) begin + // add shifted RECIP if bit is set + if (f_reg[mul_i]) acc <= acc + recip_shift; + // next bit + if (mul_i == 6'd31) begin + mul_busy <= 1'b0; // done in 32 cycles + end + mul_i <= mul_i + 6'd1; + end + end + end + + // Rounding shift to get FTW; latch when multiply finishes. + reg [PHASE_BITS-1:0] ftw_q; + wire [95:0] acc_round = acc + (96'd1 << (SHIFT-1)); + wire [PHASE_BITS-1:0] ftw_next = acc_round[SHIFT +: PHASE_BITS]; // >> SHIFT + + always @(posedge clk or negedge rst_n) begin + if (!rst_n) ftw_q <= {PHASE_BITS{1'b0}}; + else if (!mul_busy) ftw_q <= ftw_next; // update once product ready + end + + // Phase accumulator (advance at FS_HZ) + reg [PHASE_BITS-1:0] phase; + always @(posedge clk or negedge rst_n) begin + if (!rst_n) phase <= {PHASE_BITS{1'b0}}; + else if (clk_en) phase <= phase + ftw_q; + end + + // Cosine phase = sine phase + 90° + wire [PHASE_BITS-1:0] phase_cos = phase + ({{(PHASE_BITS-2){1'b0}}, 2'b01} << (PHASE_BITS-2)); + + // Quadrant & LUT index + wire [1:0] q_sin = phase [PHASE_BITS-1 -: 2]; + wire [1:0] q_cos = phase_cos[PHASE_BITS-1 -: 2]; + + wire [QTR_ADDR_BITS-1:0] idx_sin_raw = phase [PHASE_BITS-3 -: QTR_ADDR_BITS]; + wire [QTR_ADDR_BITS-1:0] idx_cos_raw = phase_cos[PHASE_BITS-3 -: QTR_ADDR_BITS]; + + wire [QTR_ADDR_BITS-1:0] idx_sin = q_sin[0] ? ({QTR_ADDR_BITS{1'b1}} - idx_sin_raw) : idx_sin_raw; + wire [QTR_ADDR_BITS-1:0] idx_cos = q_cos[0] ? ({QTR_ADDR_BITS{1'b1}} - idx_cos_raw) : idx_cos_raw; + + // 64-entry quarter-wave LUT + wire [7:0] mag_sin_u8, mag_cos_u8; + sine_qtr_lut64 u_lut_s (.addr(idx_sin), .dout(mag_sin_u8)); + sine_qtr_lut64 u_lut_c (.addr(idx_cos), .dout(mag_cos_u8)); + + // Scale to Q1.15 and apply sign + wire signed [15:0] mag_sin_q15 = {1'b0, mag_sin_u8, 7'd0}; + wire signed [15:0] mag_cos_q15 = {1'b0, mag_cos_u8, 7'd0}; + wire sin_neg = (q_sin >= 2); + wire cos_neg = (q_cos >= 2); + + wire signed [15:0] sin_next = sin_neg ? -mag_sin_q15 : mag_sin_q15; + wire signed [15:0] cos_next = cos_neg ? -mag_cos_q15 : mag_cos_q15; + + always @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + sin_q15 <= 16'sd0; cos_q15 <= 16'sd0; + end else if (clk_en) begin + sin_q15 <= sin_next; cos_q15 <= cos_next; + end + end + +endmodule + +module sine_qtr_lut64( + input wire [5:0] addr, + output reg [7:0] dout +); + always @* begin + case (addr) + 6'd0: dout = 8'd0; 6'd1: dout = 8'd6; 6'd2: dout = 8'd13; 6'd3: dout = 8'd19; + 6'd4: dout = 8'd25; 6'd5: dout = 8'd31; 6'd6: dout = 8'd37; 6'd7: dout = 8'd44; + 6'd8: dout = 8'd50; 6'd9: dout = 8'd56; 6'd10: dout = 8'd62; 6'd11: dout = 8'd68; + 6'd12: dout = 8'd74; 6'd13: dout = 8'd80; 6'd14: dout = 8'd86; 6'd15: dout = 8'd92; + 6'd16: dout = 8'd98; 6'd17: dout = 8'd103; 6'd18: dout = 8'd109; 6'd19: dout = 8'd115; + 6'd20: dout = 8'd120; 6'd21: dout = 8'd126; 6'd22: dout = 8'd131; 6'd23: dout = 8'd136; + 6'd24: dout = 8'd142; 6'd25: dout = 8'd147; 6'd26: dout = 8'd152; 6'd27: dout = 8'd157; + 6'd28: dout = 8'd162; 6'd29: dout = 8'd167; 6'd30: dout = 8'd171; 6'd31: dout = 8'd176; + 6'd32: dout = 8'd180; 6'd33: dout = 8'd185; 6'd34: dout = 8'd189; 6'd35: dout = 8'd193; + 6'd36: dout = 8'd197; 6'd37: dout = 8'd201; 6'd38: dout = 8'd205; 6'd39: dout = 8'd208; + 6'd40: dout = 8'd212; 6'd41: dout = 8'd215; 6'd42: dout = 8'd219; 6'd43: dout = 8'd222; + 6'd44: dout = 8'd225; 6'd45: dout = 8'd228; 6'd46: dout = 8'd231; 6'd47: dout = 8'd233; + 6'd48: dout = 8'd236; 6'd49: dout = 8'd238; 6'd50: dout = 8'd240; 6'd51: dout = 8'd242; + 6'd52: dout = 8'd244; 6'd53: dout = 8'd246; 6'd54: dout = 8'd247; 6'd55: dout = 8'd249; + 6'd56: dout = 8'd250; 6'd57: dout = 8'd251; 6'd58: dout = 8'd252; 6'd59: dout = 8'd253; + 6'd60: dout = 8'd254; 6'd61: dout = 8'd254; 6'd62: dout = 8'd255; 6'd63: dout = 8'd255; + default: dout=8'd0; + endcase + end +endmodule \ No newline at end of file diff --git a/cores/nco_q15/tb/tb_nco_q15.v b/cores/nco_q15/tb/tb_nco_q15.v new file mode 100644 index 0000000..0ca2e0a --- /dev/null +++ b/cores/nco_q15/tb/tb_nco_q15.v @@ -0,0 +1,43 @@ +`timescale 1ns/1ps + +module tb_nco_q15(); + // Clock and reset generation + reg clk; + reg resetn; + initial clk <= 1'b0; + initial resetn <= 1'b0; + always #4.17 clk <= !clk; + initial #40 resetn <= 1'b1; + + // Default run + initial begin + $dumpfile("out.vcd"); + $dumpvars; + #5_000_000 + $finish; + end; + + + reg [31:0] freq; + wire [15:0] sin_q15; + wire [15:0] cos_q15; + wire out_en; + + nco_q15 #(.CLK_HZ(120_000_000), .FS_HZ(40_000)) nco ( + .clk (clk), + .rst_n (resetn), + .freq_hz(freq), + .sin_q15(sin_q15), + .cos_q15(cos_q15), + .clk_en (out_en) + ); + + initial begin + freq = 32'h0; + #100 + freq = 32'd1000; + #2_500_000 + freq = 32'd2000; + end; + +endmodule \ No newline at end of file diff --git a/fusesoc.conf b/fusesoc.conf new file mode 100644 index 0000000..3eb1efc --- /dev/null +++ b/fusesoc.conf @@ -0,0 +1,6 @@ +[library.base] +location = /data/joppe/projects/fusesoc_test/cores +sync-uri = ./cores +sync-type = local +auto-sync = true + From cf7e03b9fe70250fad3d14f64000bbc6908355f5 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sat, 28 Feb 2026 18:23:39 +0100 Subject: [PATCH 02/12] Added some stuff from modem and added formal --- cores/nco_q15/nco_q15.core | 22 - cores/primitive/clkgen/clkgen.core | 49 ++ cores/primitive/clkgen/clkgen.v | 37 ++ cores/primitive/clkgen/clkgen_generic_impl.v | 29 + cores/primitive/clkgen/clkgen_spartan6.v | 79 +++ cores/primitive/jtag_if/jtag_if.core | 34 ++ cores/primitive/jtag_if/jtag_if.v | 52 ++ .../primitive/jtag_if/jtag_if_generic_impl.v | 36 ++ cores/primitive/jtag_if/jtag_if_spartan6.v | 52 ++ cores/signal/nco_q15/nco_q15.core | 40 ++ cores/{ => signal}/nco_q15/rtl/nco_q15.v | 0 cores/{ => signal}/nco_q15/tb/tb_nco_q15.v | 17 +- cores/system/test/mimas.ucf | 52 ++ cores/system/test/options.tcl | 1 + cores/system/test/rtl/toplevel.v | 80 +++ cores/system/test/test.core | 46 ++ cores/util/cdc/cdc.core | 16 + cores/util/cdc/rtl/cdc_req_resp.v | 70 +++ cores/util/cdc/rtl/cdc_strobe_data.v | 130 +++++ cores/util/clog2/clog2.core | 16 + cores/util/clog2/clog2.vh | 39 ++ .../formal/formal_wb_master_checker.v | 73 +++ .../formal/formal_wb_slave_checker.v | 68 +++ cores/wb/formal_checker/formal_checker.core | 16 + .../formal/formal_jtag_wb_bridge.v | 63 ++ .../jtag_wb_bridbe/formal/jtag_wb_bridge.sby | 14 + .../jtag_wb_bridbe/formal/stub_cdc_req_resp.v | 63 ++ cores/wb/jtag_wb_bridbe/formal/stub_jtag_if.v | 28 + cores/wb/jtag_wb_bridbe/jtag_wb_bridge.core | 64 +++ cores/wb/jtag_wb_bridbe/rtl/jtag_wb_bridge.v | 538 ++++++++++++++++++ .../tool/libjtag_wb_bridge/.gitignore | 6 + .../tool/libjtag_wb_bridge/Makefile | 43 ++ .../tool/libjtag_wb_bridge/__init__.py | 0 .../tool/libjtag_wb_bridge/argparse.cpp | 285 ++++++++++ .../tool/libjtag_wb_bridge/argparse.hpp | 74 +++ .../tool/libjtag_wb_bridge/difilent_jtag.cpp | 276 +++++++++ .../tool/libjtag_wb_bridge/digilent_jtag.hpp | 52 ++ .../tool/libjtag_wb_bridge/jtag_bridge.py | 142 +++++ .../libjtag_wb_bridge/jtag_wb_bridge_c.cpp | 113 ++++ .../tool/libjtag_wb_bridge/jtag_wb_bridge_c.h | 34 ++ .../jtag_wb_bridge_client.cpp | 181 ++++++ .../jtag_wb_bridge_client.hpp | 42 ++ .../tool/libjtag_wb_bridge/prog.cpp | 104 ++++ cores/wb/jtag_wb_bridbe/tool/test.py | 8 + cores/wb/wb_gpio/formal/formal_wb_gpio.v | 69 +++ cores/wb/wb_gpio/formal/wb_gpio.sby | 13 + cores/wb/wb_gpio/formal/wb_gpio/config.sby | 13 + cores/wb/wb_gpio/formal/wb_gpio/logfile.txt | 2 + cores/wb/wb_gpio/rtl/wb_gpio.v | 56 ++ cores/wb/wb_gpio/wb_gpio.core | 43 ++ cores/wb/wb_mem32/formal/formal_wb_mem32.v | 46 ++ cores/wb/wb_mem32/formal/wb_mem32.sby | 15 + cores/wb/wb_mem32/rtl/wb_mem32.v | 66 +++ cores/wb/wb_mem32/tb/tb_wb_mem32.v | 178 ++++++ cores/wb/wb_mem32/wb_mem32.core | 63 ++ 55 files changed, 3717 insertions(+), 31 deletions(-) delete mode 100644 cores/nco_q15/nco_q15.core create mode 100644 cores/primitive/clkgen/clkgen.core create mode 100644 cores/primitive/clkgen/clkgen.v create mode 100644 cores/primitive/clkgen/clkgen_generic_impl.v create mode 100644 cores/primitive/clkgen/clkgen_spartan6.v create mode 100644 cores/primitive/jtag_if/jtag_if.core create mode 100644 cores/primitive/jtag_if/jtag_if.v create mode 100644 cores/primitive/jtag_if/jtag_if_generic_impl.v create mode 100644 cores/primitive/jtag_if/jtag_if_spartan6.v create mode 100644 cores/signal/nco_q15/nco_q15.core rename cores/{ => signal}/nco_q15/rtl/nco_q15.v (100%) rename cores/{ => signal}/nco_q15/tb/tb_nco_q15.v (91%) create mode 100644 cores/system/test/mimas.ucf create mode 100644 cores/system/test/options.tcl create mode 100644 cores/system/test/rtl/toplevel.v create mode 100644 cores/system/test/test.core create mode 100644 cores/util/cdc/cdc.core create mode 100644 cores/util/cdc/rtl/cdc_req_resp.v create mode 100644 cores/util/cdc/rtl/cdc_strobe_data.v create mode 100644 cores/util/clog2/clog2.core create mode 100644 cores/util/clog2/clog2.vh create mode 100644 cores/wb/formal_checker/formal/formal_wb_master_checker.v create mode 100644 cores/wb/formal_checker/formal/formal_wb_slave_checker.v create mode 100644 cores/wb/formal_checker/formal_checker.core create mode 100644 cores/wb/jtag_wb_bridbe/formal/formal_jtag_wb_bridge.v create mode 100644 cores/wb/jtag_wb_bridbe/formal/jtag_wb_bridge.sby create mode 100644 cores/wb/jtag_wb_bridbe/formal/stub_cdc_req_resp.v create mode 100644 cores/wb/jtag_wb_bridbe/formal/stub_jtag_if.v create mode 100644 cores/wb/jtag_wb_bridbe/jtag_wb_bridge.core create mode 100644 cores/wb/jtag_wb_bridbe/rtl/jtag_wb_bridge.v create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/.gitignore create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/Makefile create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/__init__.py create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.cpp create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.hpp create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/difilent_jtag.cpp create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/digilent_jtag.hpp create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_bridge.py create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp create mode 100644 cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/prog.cpp create mode 100644 cores/wb/jtag_wb_bridbe/tool/test.py create mode 100644 cores/wb/wb_gpio/formal/formal_wb_gpio.v create mode 100644 cores/wb/wb_gpio/formal/wb_gpio.sby create mode 100644 cores/wb/wb_gpio/formal/wb_gpio/config.sby create mode 100644 cores/wb/wb_gpio/formal/wb_gpio/logfile.txt create mode 100644 cores/wb/wb_gpio/rtl/wb_gpio.v create mode 100644 cores/wb/wb_gpio/wb_gpio.core create mode 100644 cores/wb/wb_mem32/formal/formal_wb_mem32.v create mode 100644 cores/wb/wb_mem32/formal/wb_mem32.sby create mode 100644 cores/wb/wb_mem32/rtl/wb_mem32.v create mode 100644 cores/wb/wb_mem32/tb/tb_wb_mem32.v create mode 100644 cores/wb/wb_mem32/wb_mem32.core diff --git a/cores/nco_q15/nco_q15.core b/cores/nco_q15/nco_q15.core deleted file mode 100644 index 6a47f5b..0000000 --- a/cores/nco_q15/nco_q15.core +++ /dev/null @@ -1,22 +0,0 @@ -CAPI=2: - -name: joppeb:base:nco_q15:1.0 - -filesets: - rtl: - files: - - rtl/nco_q15.v - file_type: verilogSource - - tb: - files: - - tb/tb_nco_q15.v - file_type: verilogSource - -targets: - sim: - default_tool: icarus - filesets: - - rtl - - tb - toplevel: tb_nco_q15 \ No newline at end of file diff --git a/cores/primitive/clkgen/clkgen.core b/cores/primitive/clkgen/clkgen.core new file mode 100644 index 0000000..cf8b593 --- /dev/null +++ b/cores/primitive/clkgen/clkgen.core @@ -0,0 +1,49 @@ +CAPI=2: + +name: joppeb:primitive:clkgen:1.0 +description: Parameterized clock generator wrapper + +filesets: + wrapper: + files: + - clkgen.v + file_type: verilogSource + generic: + files: + - clkgen_generic_impl.v + file_type: verilogSource + spartan6: + files: + - clkgen_spartan6.v + file_type: verilogSource + +targets: + default: + filesets: + - wrapper + - generic + - spartan6 + toplevel: clkgen + parameters: + - CLK_IN_HZ + - CLKFX_DIVIDE + - CLKFX_MULTIPLY + - CLKDV_DIVIDE + +parameters: + CLK_IN_HZ: + datatype: int + description: Input clock frequency in Hz + paramtype: vlogparam + CLKFX_DIVIDE: + datatype: int + description: DCM CLKFX divide value + paramtype: vlogparam + CLKFX_MULTIPLY: + datatype: int + description: DCM CLKFX multiply value + paramtype: vlogparam + CLKDV_DIVIDE: + datatype: real + description: DCM CLKDV divide value + paramtype: vlogparam diff --git a/cores/primitive/clkgen/clkgen.v b/cores/primitive/clkgen/clkgen.v new file mode 100644 index 0000000..b2a90c5 --- /dev/null +++ b/cores/primitive/clkgen/clkgen.v @@ -0,0 +1,37 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Clock generator +// Stable public wrapper that selects the implementation. +// ============================================================================= +module clkgen #( + parameter integer CLK_IN_HZ = 100000000, + parameter integer CLKFX_DIVIDE = 20, + parameter integer CLKFX_MULTIPLY = 3, + parameter real CLKDV_DIVIDE = 2.0 +)( + input wire clk_in, + output wire clk_out +); +`ifdef FPGA_SPARTAN6 + clkgen_spartan6_impl #( + .CLK_IN_HZ(CLK_IN_HZ), + .CLKFX_DIVIDE(CLKFX_DIVIDE), + .CLKFX_MULTIPLY(CLKFX_MULTIPLY), + .CLKDV_DIVIDE(CLKDV_DIVIDE) + ) impl_i ( + .clk_in(clk_in), + .clk_out(clk_out) + ); +`else + clkgen_generic_impl #( + .CLK_IN_HZ(CLK_IN_HZ), + .CLKFX_DIVIDE(CLKFX_DIVIDE), + .CLKFX_MULTIPLY(CLKFX_MULTIPLY), + .CLKDV_DIVIDE(CLKDV_DIVIDE) + ) impl_i ( + .clk_in(clk_in), + .clk_out(clk_out) + ); +`endif +endmodule diff --git a/cores/primitive/clkgen/clkgen_generic_impl.v b/cores/primitive/clkgen/clkgen_generic_impl.v new file mode 100644 index 0000000..a3addd8 --- /dev/null +++ b/cores/primitive/clkgen/clkgen_generic_impl.v @@ -0,0 +1,29 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Clock generator +// Generic behavioural model. This is intended for simulation only. +// ============================================================================= +module clkgen_generic_impl #( + parameter integer CLK_IN_HZ = 100000000, + parameter integer CLKFX_DIVIDE = 20, + parameter integer CLKFX_MULTIPLY = 3, + parameter real CLKDV_DIVIDE = 2.0 +)( + input wire clk_in, + output reg clk_out +); + real half_period_ns; + + initial begin + clk_out = 1'b0; + half_period_ns = (500000000.0 * CLKFX_DIVIDE) / (CLK_IN_HZ * CLKFX_MULTIPLY); + + // Start oscillation after the source clock becomes active. + @(posedge clk_in); + forever #(half_period_ns) clk_out = ~clk_out; + end + + wire _unused_clkdv_divide; + assign _unused_clkdv_divide = (CLKDV_DIVIDE != 0.0); +endmodule diff --git a/cores/primitive/clkgen/clkgen_spartan6.v b/cores/primitive/clkgen/clkgen_spartan6.v new file mode 100644 index 0000000..c761a6c --- /dev/null +++ b/cores/primitive/clkgen/clkgen_spartan6.v @@ -0,0 +1,79 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Clock generator +// Spartan-6 DCM wrapper with parameterized input and output ratios. +// ============================================================================= +module clkgen_spartan6_impl #( + parameter integer CLK_IN_HZ = 100000000, + parameter integer CLKFX_DIVIDE = 20, + parameter integer CLKFX_MULTIPLY = 3, + parameter real CLKDV_DIVIDE = 2.0 +)( + input wire clk_in, + output wire clk_out +); +`ifdef FPGA_SPARTAN6 + localparam real CLKIN_PERIOD_NS = 1000000000.0 / CLK_IN_HZ; + + wire clkfb; + wire clk0; + wire clkfx; + wire locked_unused; + wire [7:0] status_unused; + + DCM_SP #( + .CLKDV_DIVIDE(CLKDV_DIVIDE), + .CLKFX_DIVIDE(CLKFX_DIVIDE), + .CLKFX_MULTIPLY(CLKFX_MULTIPLY), + .CLKIN_DIVIDE_BY_2("FALSE"), + .CLKIN_PERIOD(CLKIN_PERIOD_NS), + .CLKOUT_PHASE_SHIFT("NONE"), + .CLK_FEEDBACK("1X"), + .DESKEW_ADJUST("SYSTEM_SYNCHRONOUS"), + .PHASE_SHIFT(0), + .STARTUP_WAIT("FALSE") + ) dcm_sp_i ( + .CLKIN(clk_in), + .CLKFB(clkfb), + .CLK0(clk0), + .CLK90(), + .CLK180(), + .CLK270(), + .CLK2X(), + .CLK2X180(), + .CLKFX(clkfx), + .CLKFX180(), + .CLKDV(), + .PSCLK(1'b0), + .PSEN(1'b0), + .PSINCDEC(1'b0), + .PSDONE(), + .LOCKED(locked_unused), + .STATUS(status_unused), + .RST(1'b0), + .DSSEN(1'b0) + ); + + BUFG clkfb_buf_i ( + .I(clk0), + .O(clkfb) + ); + + BUFG clkout_buf_i ( + .I(clkfx), + .O(clk_out) + ); +`else + assign clk_out = 1'b0; + + wire _unused_clk_in; + wire _unused_clkfx_divide; + wire _unused_clkfx_multiply; + wire _unused_clkdv_divide; + assign _unused_clk_in = clk_in; + assign _unused_clkfx_divide = CLKFX_DIVIDE[0]; + assign _unused_clkfx_multiply = CLKFX_MULTIPLY[0]; + assign _unused_clkdv_divide = (CLKDV_DIVIDE != 0.0); +`endif +endmodule diff --git a/cores/primitive/jtag_if/jtag_if.core b/cores/primitive/jtag_if/jtag_if.core new file mode 100644 index 0000000..965a1ef --- /dev/null +++ b/cores/primitive/jtag_if/jtag_if.core @@ -0,0 +1,34 @@ +CAPI=2: + +name: joppeb:primitive:jtag_if:1.0 +description: JTAG user chain interface + +filesets: + wrapper: + files: + - jtag_if.v + file_type: verilogSource + generic: + files: + - jtag_if_generic_impl.v + file_type: verilogSource + spartan6: + files: + - jtag_if_spartan6.v + file_type: verilogSource + +targets: + default: + filesets: + - wrapper + - generic + - spartan6 + toplevel: jtag_if + parameters: + - chain + +parameters: + chain: + datatype: int + description: User chain + paramtype: vlogparam diff --git a/cores/primitive/jtag_if/jtag_if.v b/cores/primitive/jtag_if/jtag_if.v new file mode 100644 index 0000000..f4eee7f --- /dev/null +++ b/cores/primitive/jtag_if/jtag_if.v @@ -0,0 +1,52 @@ +`timescale 1ns/1ps + +// ============================================================================= +// JTAG interface +// Stable public wrapper that selects an implementation. +// ============================================================================= +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 +); +`ifdef FPGA_SPARTAN6 + jtag_if_spartan6_impl #( + .chain(chain) + ) impl_i ( + .i_tdo(i_tdo), + .o_tck(o_tck), + .o_tdi(o_tdi), + .o_drck(o_drck), + .o_capture(o_capture), + .o_shift(o_shift), + .o_update(o_update), + .o_runtest(o_runtest), + .o_reset(o_reset), + .o_sel(o_sel) + ); +`else + jtag_if_generic_impl #( + .chain(chain) + ) impl_i ( + .i_tdo(i_tdo), + .o_tck(o_tck), + .o_tdi(o_tdi), + .o_drck(o_drck), + .o_capture(o_capture), + .o_shift(o_shift), + .o_update(o_update), + .o_runtest(o_runtest), + .o_reset(o_reset), + .o_sel(o_sel) + ); +`endif +endmodule diff --git a/cores/primitive/jtag_if/jtag_if_generic_impl.v b/cores/primitive/jtag_if/jtag_if_generic_impl.v new file mode 100644 index 0000000..c08919d --- /dev/null +++ b/cores/primitive/jtag_if/jtag_if_generic_impl.v @@ -0,0 +1,36 @@ +`timescale 1ns/1ps + +// ============================================================================= +// JTAG interface +// Generic stub model with inactive/tied-off outputs. +// ============================================================================= +module jtag_if_generic_impl #( + 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; + + // Keep lint tools quiet in generic builds where TDO is unused. + wire _unused_tdo; + wire _unused_chain; + assign _unused_tdo = i_tdo; + assign _unused_chain = chain[0]; +endmodule diff --git a/cores/primitive/jtag_if/jtag_if_spartan6.v b/cores/primitive/jtag_if/jtag_if_spartan6.v new file mode 100644 index 0000000..a54bfe8 --- /dev/null +++ b/cores/primitive/jtag_if/jtag_if_spartan6.v @@ -0,0 +1,52 @@ +`timescale 1ns/1ps + +// ============================================================================= +// JTAG interface +// Spartan-6 BSCAN primitive wrapper (USER1 chain). +// ============================================================================= +module jtag_if_spartan6_impl #( + 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 +); +`ifdef FPGA_SPARTAN6 + BSCAN_SPARTAN6 #( + .JTAG_CHAIN(chain) + ) bscan_i ( + .CAPTURE(o_capture), + .DRCK(o_drck), + .RESET(o_reset), + .RUNTEST(o_runtest), + .SEL(o_sel), + .SHIFT(o_shift), + .TCK(o_tck), + .TDI(o_tdi), + .TDO(i_tdo), + .UPDATE(o_update) + ); +`else + 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_tdo; + wire _unused_chain; + assign _unused_tdo = i_tdo; + assign _unused_chain = chain[0]; +`endif +endmodule diff --git a/cores/signal/nco_q15/nco_q15.core b/cores/signal/nco_q15/nco_q15.core new file mode 100644 index 0000000..6db61d5 --- /dev/null +++ b/cores/signal/nco_q15/nco_q15.core @@ -0,0 +1,40 @@ +CAPI=2: + +name: joppeb:signal:nco_q15:1.0 +description: A number controlled sine and cosine oscillator + +filesets: + rtl: + files: + - rtl/nco_q15.v + file_type: verilogSource + + tb: + files: + - tb/tb_nco_q15.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: nco_q15 + parameters: + - CLK_HZ + - FS_HZ + sim: + default_tool: icarus + filesets: + - rtl + - tb + toplevel: tb_nco_q15 + +parameters: + CLK_HZ: + datatype: int + description: Frequency of the input clock + paramtype: vlogparam + FS_HZ: + datatype: int + description: Sample Frequency + paramtype: vlogparam \ No newline at end of file diff --git a/cores/nco_q15/rtl/nco_q15.v b/cores/signal/nco_q15/rtl/nco_q15.v similarity index 100% rename from cores/nco_q15/rtl/nco_q15.v rename to cores/signal/nco_q15/rtl/nco_q15.v diff --git a/cores/nco_q15/tb/tb_nco_q15.v b/cores/signal/nco_q15/tb/tb_nco_q15.v similarity index 91% rename from cores/nco_q15/tb/tb_nco_q15.v rename to cores/signal/nco_q15/tb/tb_nco_q15.v index 0ca2e0a..a85cb53 100644 --- a/cores/nco_q15/tb/tb_nco_q15.v +++ b/cores/signal/nco_q15/tb/tb_nco_q15.v @@ -9,15 +9,6 @@ module tb_nco_q15(); always #4.17 clk <= !clk; initial #40 resetn <= 1'b1; - // Default run - initial begin - $dumpfile("out.vcd"); - $dumpvars; - #5_000_000 - $finish; - end; - - reg [31:0] freq; wire [15:0] sin_q15; wire [15:0] cos_q15; @@ -33,11 +24,19 @@ module tb_nco_q15(); ); initial begin + $dumpfile("out.vcd"); + $dumpvars; + freq = 32'h0; #100 freq = 32'd1000; #2_500_000 freq = 32'd2000; + #2_500_000 + freq = 32'd600; + #2_500_000 + + $finish; end; endmodule \ No newline at end of file diff --git a/cores/system/test/mimas.ucf b/cores/system/test/mimas.ucf new file mode 100644 index 0000000..9c79021 --- /dev/null +++ b/cores/system/test/mimas.ucf @@ -0,0 +1,52 @@ +# Main clock input +NET "aclk" LOC = P126; +NET "aclk" TNM_NET = "SYS_CLK_PIN"; +TIMESPEC TS_SYS_CLK_PIN = PERIOD "SYS_CLK_PIN" 10 ns HIGH 50 %; + +# Boards button row +NET "aresetn" LOC = P120; +NET "aresetn" IOSTANDARD = LVCMOS33; +NET "aresetn" PULLUP; + +NET "led_green" LOC = P29; +NET "led_green" IOSTANDARD = LVCMOS33; +NET "led_red" LOC = P26; +NET "led_red" IOSTANDARD = LVCMOS33; + +NET "r2r[0]" LOC = P131; +NET "r2r[1]" LOC = P133; +NET "r2r[2]" LOC = P137; +NET "r2r[3]" LOC = P139; +NET "r2r[4]" LOC = P141; +NET "r2r[5]" LOC = P1; +NET "r2r[0]" IOSTANDARD = LVCMOS33; +NET "r2r[1]" IOSTANDARD = LVCMOS33; +NET "r2r[2]" IOSTANDARD = LVCMOS33; +NET "r2r[3]" IOSTANDARD = LVCMOS33; +NET "r2r[4]" IOSTANDARD = LVCMOS33; +NET "r2r[5]" IOSTANDARD = LVCMOS33; + +NET "LED[0]" LOC = P119; +NET "LED[0]" IOSTANDARD = LVCMOS33; +NET "LED[0]" DRIVE = 8; +NET "LED[1]" LOC = P118; +NET "LED[1]" IOSTANDARD = LVCMOS33; +NET "LED[1]" DRIVE = 8; +NET "LED[2]" LOC = P117; +NET "LED[2]" IOSTANDARD = LVCMOS33; +NET "LED[2]" DRIVE = 8; +NET "LED[3]" LOC = P116; +NET "LED[3]" IOSTANDARD = LVCMOS33; +NET "LED[3]" DRIVE = 8; +NET "LED[4]" LOC = P115; +NET "LED[4]" IOSTANDARD = LVCMOS33; +NET "LED[4]" DRIVE = 8; +NET "LED[5]" LOC = P114; +NET "LED[5]" IOSTANDARD = LVCMOS33; +NET "LED[5]" DRIVE = 8; +NET "LED[6]" LOC = P112; +NET "LED[6]" IOSTANDARD = LVCMOS33; +NET "LED[6]" DRIVE = 8; +NET "LED[7]" LOC = P111; +NET "LED[7]" IOSTANDARD = LVCMOS33; +NET "LED[7]" DRIVE = 8; \ No newline at end of file diff --git a/cores/system/test/options.tcl b/cores/system/test/options.tcl new file mode 100644 index 0000000..dcd3c00 --- /dev/null +++ b/cores/system/test/options.tcl @@ -0,0 +1 @@ +project set "Create Binary Configuration File" TRUE -process "Generate Programming File" \ No newline at end of file diff --git a/cores/system/test/rtl/toplevel.v b/cores/system/test/rtl/toplevel.v new file mode 100644 index 0000000..9a7e348 --- /dev/null +++ b/cores/system/test/rtl/toplevel.v @@ -0,0 +1,80 @@ +`timescale 1ns/1ps + +module toplevel( + input wire aclk, + input wire aresetn, + + output wire led_green, + output wire led_red, + + output wire[5:0] r2r, + output wire[7:0] LED + +); + + // Clocking + wire clk_100; + assign clk_100 = aclk; + wire clk_15; + clkgen #( + .CLK_IN_HZ(100000000), + .CLKFX_DIVIDE(20), + .CLKFX_MULTIPLY(3) + ) clk_gen_15 ( + .clk_in(clk_100), + .clk_out(clk_15) + ); + + wire wb_rst; + assign wb_rst = ~aresetn; + + wire [31:0] wb_adr; + wire [31:0] wb_dat_w; + wire [31:0] wb_dat_r; + wire [3:0] wb_sel; + wire wb_we; + wire wb_cyc; + wire wb_stb; + wire wb_ack; + wire wb_cmd_reset; + + wire [31:0] gpio_out; + wire gpio_rst; + assign gpio_rst = wb_rst; + + jtag_wb_bridge u_jtag_wb_bridge ( + .i_clk(clk_15), + .i_rst(wb_rst), + .o_wb_adr(wb_adr), + .o_wb_dat(wb_dat_w), + .o_wb_sel(wb_sel), + .o_wb_we(wb_we), + .o_wb_cyc(wb_cyc), + .o_wb_stb(wb_stb), + .i_wb_rdt(wb_dat_r), + .i_wb_ack(wb_ack), + .o_cmd_reset(wb_cmd_reset) + ); + + wb_gpio #( + .address(32'h00000000) + ) u_wb_gpio ( + .i_wb_clk(clk_15), + .i_wb_rst(gpio_rst), + .i_wb_adr(wb_adr), + .i_wb_dat(wb_dat_w), + .i_wb_sel(wb_sel), + .i_wb_we(wb_we), + .i_wb_stb(wb_cyc & wb_stb), + .i_gpio(gpio_out), + .o_wb_rdt(wb_dat_r), + .o_wb_ack(wb_ack), + .o_gpio(gpio_out) + ); + + assign led_green = aresetn; + assign led_red = wb_cmd_reset; + assign LED = gpio_out[7:0]; + assign r2r = gpio_out[13:8]; + +endmodule diff --git a/cores/system/test/test.core b/cores/system/test/test.core new file mode 100644 index 0000000..4697ce0 --- /dev/null +++ b/cores/system/test/test.core @@ -0,0 +1,46 @@ +CAPI=2: + +name: joppeb:system:test:1.0 +description: Example top-level + +filesets: + rtl: + depend: + - joppeb:primitive:clkgen + - joppeb:wb:jtag_wb_bridge + - joppeb:wb:wb_gpio + files: + - rtl/toplevel.v + file_type: verilogSource + + mimas: + files: + - mimas.ucf : {file_type : UCF} + - options.tcl : {file_type : tclSource} + +targets: + default: + filesets: + - rtl + toplevel: toplevel + + mimas: + filesets: + - rtl + - mimas + toplevel: toplevel + parameters: + - FPGA_SPARTAN6=true + default_tool: ise + tools: + ise: + family: Spartan6 + device: xc6slx9 + package: tqg144 + speed: -2 + +parameters: + FPGA_SPARTAN6: + datatype: bool + description: Select Spartan-6 family specific implementations + paramtype: vlogdefine diff --git a/cores/util/cdc/cdc.core b/cores/util/cdc/cdc.core new file mode 100644 index 0000000..adbeb2e --- /dev/null +++ b/cores/util/cdc/cdc.core @@ -0,0 +1,16 @@ +CAPI=2: + +name: joppeb:util:cdc:1.0 +description: Clock-domain crossing helpers + +filesets: + rtl: + files: + - rtl/cdc_strobe_data.v + - rtl/cdc_req_resp.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl diff --git a/cores/util/cdc/rtl/cdc_req_resp.v b/cores/util/cdc/rtl/cdc_req_resp.v new file mode 100644 index 0000000..0a7d826 --- /dev/null +++ b/cores/util/cdc/rtl/cdc_req_resp.v @@ -0,0 +1,70 @@ +`timescale 1 ns/1 ps +// ============================================================================= +// cdc_req_resp +// Bidirectional channel made from two cdc_strobe_data mailboxes. +// ============================================================================= +module cdc_req_resp #( + parameter integer REQ_W = 32, + parameter integer RESP_W = 32, + parameter integer STABLE_SAMPLES = 2 +)( + // Side A (e.g., JTAG/TCK) + 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, + + // Side B (e.g., system/i_clk) + input wire b_clk, + input wire b_rst, + + output wire b_req_pulse, + output wire [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 +); + + cdc_strobe_data #( + .WIDTH(REQ_W), + .STABLE_SAMPLES(STABLE_SAMPLES) + ) u_req ( + .s_clk(a_clk), + .s_rst(a_rst), + .s_pulse(a_req_pulse), + .s_data(a_req_data), + .s_busy(a_req_busy), + .s_accepted(a_req_accepted), + + .d_clk(b_clk), + .d_rst(b_rst), + .d_pulse(b_req_pulse), + .d_data(b_req_data) + ); + + cdc_strobe_data #( + .WIDTH(RESP_W), + .STABLE_SAMPLES(STABLE_SAMPLES) + ) u_resp ( + .s_clk(b_clk), + .s_rst(b_rst), + .s_pulse(b_resp_pulse), + .s_data(b_resp_data), + .s_busy(b_resp_busy), + .s_accepted(b_resp_accepted), + + .d_clk(a_clk), + .d_rst(a_rst), + .d_pulse(a_resp_pulse), + .d_data(a_resp_data) + ); + +endmodule diff --git a/cores/util/cdc/rtl/cdc_strobe_data.v b/cores/util/cdc/rtl/cdc_strobe_data.v new file mode 100644 index 0000000..bc3772d --- /dev/null +++ b/cores/util/cdc/rtl/cdc_strobe_data.v @@ -0,0 +1,130 @@ +`timescale 1 ns/1 ps +// ============================================================================= +// cdc_strobe_data +// - One-deep mailbox for (strobe + data) crossing clock domains. +// - Uses toggle req/ack with 2FF sync for toggles. +// - Wide bus is held stable by source until ack, destination samples-until-stable. +// ============================================================================= +module cdc_strobe_data #( + parameter integer WIDTH = 32, + parameter integer STABLE_SAMPLES = 2 // >=2 recommended +)( + // Source domain + input wire s_clk, + input wire s_rst, // async OK (posedge) if used consistently + input wire s_pulse, // strobe (1+ cycles). Accepted when not busy. + input wire [WIDTH-1:0] s_data, + output wire s_busy, // 1 = mailbox full / waiting for ack + output wire s_accepted, // 1-cycle pulse when we accepted s_pulse + + // Destination domain + input wire d_clk, + input wire d_rst, + output reg d_pulse, // 1-cycle pulse on new data + output reg [WIDTH-1:0] d_data // updated when d_pulse asserted; held otherwise +); + + // ---------------------------- + // Source: hold + req toggle + // ---------------------------- + reg [WIDTH-1:0] s_hold; + reg s_req_tog; + reg s_inflight; + + // Ack toggle synchronized into source domain + (* ASYNC_REG="TRUE" *) reg s_ack_sync1, s_ack_sync2; + + assign s_busy = s_inflight; + wire do_accept = s_pulse && !s_inflight; + assign s_accepted = do_accept; + + // d_ack_tog is generated in destination domain (declared below as reg) + // and is synced here with 2FF. + always @(posedge s_clk or posedge s_rst) begin + if (s_rst) begin + s_hold <= {WIDTH{1'b0}}; + s_req_tog <= 1'b0; + s_inflight <= 1'b0; + s_ack_sync1 <= 1'b0; + s_ack_sync2 <= 1'b0; + end else begin + s_ack_sync1 <= d_ack_tog; + s_ack_sync2 <= s_ack_sync1; + + // clear inflight when ack matches current req toggle + if (s_inflight && (s_ack_sync2 == s_req_tog)) + s_inflight <= 1'b0; + + // accept new item + if (do_accept) begin + s_hold <= s_data; + s_req_tog <= ~s_req_tog; + s_inflight <= 1'b1; + end + end + end + + // ---------------------------- + // Destination: sync req toggle, sample-until-stable, then ack toggle + // ---------------------------- + (* ASYNC_REG="TRUE" *) reg d_req_sync1, d_req_sync2; + reg d_req_seen; + + reg d_ack_tog; + + reg [WIDTH-1:0] samp; + reg [WIDTH-1:0] samp_prev; + integer stable_cnt; + reg capturing; + + wire d_new_req = (d_req_sync2 != d_req_seen); + + always @(posedge d_clk or posedge d_rst) begin + if (d_rst) begin + d_req_sync1 <= 1'b0; + d_req_sync2 <= 1'b0; + d_req_seen <= 1'b0; + d_ack_tog <= 1'b0; + + d_pulse <= 1'b0; + d_data <= {WIDTH{1'b0}}; + + samp <= {WIDTH{1'b0}}; + samp_prev <= {WIDTH{1'b0}}; + stable_cnt <= 0; + capturing <= 1'b0; + end else begin + d_pulse <= 1'b0; + + d_req_sync1 <= s_req_tog; + d_req_sync2 <= d_req_sync1; + + if (d_new_req && !capturing) begin + capturing <= 1'b1; + stable_cnt <= 0; + samp_prev <= s_hold; + samp <= s_hold; + end else if (capturing) begin + samp <= s_hold; + + if (samp == samp_prev) begin + if (stable_cnt < (STABLE_SAMPLES-1)) + stable_cnt <= stable_cnt + 1; + else begin + // accept + d_data <= samp; + d_pulse <= 1'b1; + d_req_seen <= d_req_sync2; + d_ack_tog <= ~d_ack_tog; + capturing <= 1'b0; + end + end else begin + stable_cnt <= 0; + end + + samp_prev <= samp; + end + end + end + +endmodule \ No newline at end of file diff --git a/cores/util/clog2/clog2.core b/cores/util/clog2/clog2.core new file mode 100644 index 0000000..3f6f0c2 --- /dev/null +++ b/cores/util/clog2/clog2.core @@ -0,0 +1,16 @@ +CAPI=2: + +name: joppeb:util:clog2:1.0 +description: Verilog-2001 compatible ceil(log2(x)) macro header + +filesets: + include: + files: + - clog2.vh: + is_include_file: true + file_type: verilogSource + +targets: + default: + filesets: + - include diff --git a/cores/util/clog2/clog2.vh b/cores/util/clog2/clog2.vh new file mode 100644 index 0000000..0aad108 --- /dev/null +++ b/cores/util/clog2/clog2.vh @@ -0,0 +1,39 @@ +`ifndef CLOG2_VH +`define CLOG2_VH + +// Verilog-2001 compatible ceil(log2(x)) macro (matches $clog2 semantics). +`define CLOG2(x) \ + (((x) <= 1) ? 0 : \ + ((x) <= 2) ? 1 : \ + ((x) <= 4) ? 2 : \ + ((x) <= 8) ? 3 : \ + ((x) <= 16) ? 4 : \ + ((x) <= 32) ? 5 : \ + ((x) <= 64) ? 6 : \ + ((x) <= 128) ? 7 : \ + ((x) <= 256) ? 8 : \ + ((x) <= 512) ? 9 : \ + ((x) <= 1024) ? 10 : \ + ((x) <= 2048) ? 11 : \ + ((x) <= 4096) ? 12 : \ + ((x) <= 8192) ? 13 : \ + ((x) <= 16384) ? 14 : \ + ((x) <= 32768) ? 15 : \ + ((x) <= 65536) ? 16 : \ + ((x) <= 131072) ? 17 : \ + ((x) <= 262144) ? 18 : \ + ((x) <= 524288) ? 19 : \ + ((x) <= 1048576) ? 20 : \ + ((x) <= 2097152) ? 21 : \ + ((x) <= 4194304) ? 22 : \ + ((x) <= 8388608) ? 23 : \ + ((x) <= 16777216) ? 24 : \ + ((x) <= 33554432) ? 25 : \ + ((x) <= 67108864) ? 26 : \ + ((x) <= 134217728) ? 27 : \ + ((x) <= 268435456) ? 28 : \ + ((x) <= 536870912) ? 29 : \ + ((x) <= 1073741824) ? 30 : \ + ((x) <= 2147483648) ? 31 : 32) + +`endif diff --git a/cores/wb/formal_checker/formal/formal_wb_master_checker.v b/cores/wb/formal_checker/formal/formal_wb_master_checker.v new file mode 100644 index 0000000..c2addac --- /dev/null +++ b/cores/wb/formal_checker/formal/formal_wb_master_checker.v @@ -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 diff --git a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v new file mode 100644 index 0000000..68db9d7 --- /dev/null +++ b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v @@ -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 diff --git a/cores/wb/formal_checker/formal_checker.core b/cores/wb/formal_checker/formal_checker.core new file mode 100644 index 0000000..3da7e5d --- /dev/null +++ b/cores/wb/formal_checker/formal_checker.core @@ -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 diff --git a/cores/wb/jtag_wb_bridbe/formal/formal_jtag_wb_bridge.v b/cores/wb/jtag_wb_bridbe/formal/formal_jtag_wb_bridge.v new file mode 100644 index 0000000..e43ea7b --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/formal/formal_jtag_wb_bridge.v @@ -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 diff --git a/cores/wb/jtag_wb_bridbe/formal/jtag_wb_bridge.sby b/cores/wb/jtag_wb_bridbe/formal/jtag_wb_bridge.sby new file mode 100644 index 0000000..3aa1f4b --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/formal/jtag_wb_bridge.sby @@ -0,0 +1,14 @@ +[options] +mode prove +depth 8 + +[engines] +smtbmc z3 + +[script] +{{"-formal"|gen_reads}} +prep -top {{top_level}} +clk2fflogic + +[files] +{{files}} diff --git a/cores/wb/jtag_wb_bridbe/formal/stub_cdc_req_resp.v b/cores/wb/jtag_wb_bridbe/formal/stub_cdc_req_resp.v new file mode 100644 index 0000000..2ffae74 --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/formal/stub_cdc_req_resp.v @@ -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 diff --git a/cores/wb/jtag_wb_bridbe/formal/stub_jtag_if.v b/cores/wb/jtag_wb_bridbe/formal/stub_jtag_if.v new file mode 100644 index 0000000..ee2f9a6 --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/formal/stub_jtag_if.v @@ -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 diff --git a/cores/wb/jtag_wb_bridbe/jtag_wb_bridge.core b/cores/wb/jtag_wb_bridbe/jtag_wb_bridge.core new file mode 100644 index 0000000..b0a9006 --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/jtag_wb_bridge.core @@ -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 diff --git a/cores/wb/jtag_wb_bridbe/rtl/jtag_wb_bridge.v b/cores/wb/jtag_wb_bridbe/rtl/jtag_wb_bridge.v new file mode 100644 index 0000000..bfb9e9e --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/rtl/jtag_wb_bridge.v @@ -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 diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/.gitignore b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/.gitignore new file mode 100644 index 0000000..e2ff95c --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.d +*.o +*.a +*.so +prog \ No newline at end of file diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/Makefile b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/Makefile new file mode 100644 index 0000000..74594cf --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/Makefile @@ -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) diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/__init__.py b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.cpp b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.cpp new file mode 100644 index 0000000..04c2c8f --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.cpp @@ -0,0 +1,285 @@ +#include "argparse.hpp" + +#include +#include +#include +#include +#include + +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(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(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 << " "; + } + 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(); +} diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.hpp b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.hpp new file mode 100644 index 0000000..edc6dff --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include + +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 order_; + std::unordered_map meta_; + + std::unordered_map string_values_; + std::unordered_map int_values_; + std::unordered_map flag_values_; + std::unordered_map provided_; + std::unordered_map short_to_long_; +}; diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/difilent_jtag.cpp b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/difilent_jtag.cpp new file mode 100644 index 0000000..4de2307 --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/difilent_jtag.cpp @@ -0,0 +1,276 @@ +#include "digilent_jtag.hpp" + +#include +#include + +#include +#include + +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((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(1u << (bit_idx % 8)); + if (bit & 0x1u) { + packed_bits[bit_idx / 8] |= mask; + } else { + packed_bits[bit_idx / 8] &= static_cast(~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 sel(selector.begin(), selector.end()); + sel.push_back('\0'); + + if (!DmgrOpen(&hif_, sel.data())) { + hif_ = hifInvalid; + return setErrorFromDmgr("DmgrOpen"); + } + + if (!DjtgEnableEx(hif_, static_cast(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(requested_hz), &actual)) { + return setErrorFromDmgr("DjtgSetSpeed"); + } + if (actual_hz) { + *actual_hz = static_cast(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 tx(static_cast((ir_bits + 7) / 8), 0); + for (int i = 0; i < ir_bits && i < 64; ++i) { + if ((instruction >> i) & 0x1u) { + tx[static_cast(i / 8)] |= static_cast(1u << (i % 8)); + } + } + std::vector 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((bit_count + 7) / 8); + std::vector tx_zeros; + if (!tx_bits) { + tx_zeros.assign(nbytes, 0); + tx_bits = tx_zeros.data(); + } + + std::vector 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& tx_bits, std::vector* rx_bits, int bit_count) { + if (bit_count <= 0) { + return setError("shiftData(vector): bit_count must be > 0"); + } + const size_t nbytes = static_cast((bit_count + 7) / 8); + if (tx_bits.size() < nbytes) { + return setError("shiftData(vector): tx_bits is smaller than required bit_count"); + } + + std::vector 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(tms_bits), nullptr, static_cast(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(tx_bits), + rx_bits, + static_cast(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; +} diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/digilent_jtag.hpp b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/digilent_jtag.hpp new file mode 100644 index 0000000..e792722 --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/digilent_jtag.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +#include + +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& tx_bits, std::vector* 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_; +}; diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_bridge.py b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_bridge.py new file mode 100644 index 0000000..70c435c --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_bridge.py @@ -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 diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp new file mode 100644 index 0000000..71812ea --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp @@ -0,0 +1,113 @@ +#include "jtag_wb_bridge_c.h" + +#include "jtag_wb_bridge_client.hpp" + +#include + +struct JtagBridgeHandle { + JtagWishboneBridge bridge; +}; + +namespace { + +template +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" diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h new file mode 100644 index 0000000..a14829c --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#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 diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp new file mode 100644 index 0000000..c3df11b --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp @@ -0,0 +1,181 @@ +#include "jtag_wb_bridge_client.hpp" + +#include + +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(data); + out[1] = static_cast(data >> 8); + out[2] = static_cast(data >> 16); + out[3] = static_cast(data >> 24); + out[4] = static_cast(addr); + out[5] = static_cast(addr >> 8); + out[6] = static_cast(addr >> 16); + out[7] = static_cast(addr >> 24); + out[8] = opcode; +} + +uint32_t getResponseData32(const uint8_t rx[kPacketBytes]) { + return static_cast(rx[2]) | + (static_cast(rx[3]) << 8) | + (static_cast(rx[4]) << 16) | + (static_cast(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(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(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; +} diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp new file mode 100644 index 0000000..53679b7 --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "digilent_jtag.hpp" + +#include +#include + +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_; +}; diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/prog.cpp b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/prog.cpp new file mode 100644 index 0000000..7be3c58 --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/prog.cpp @@ -0,0 +1,104 @@ +#include "argparse.hpp" +#include "jtag_wb_bridge_client.hpp" + +#include +#include + +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(std::fread(buf, sizeof(uint32_t), 32, f)); + for (int i = 0; i < nr; ++i) { + if (!bridge.write32(addr + static_cast(i * 4), buf[i])) { + std::printf("Write failed at %04x: %s\n", + addr + static_cast(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(i * 4), &value)) { + std::printf("Read failed at %04x: %s\n", + addr + static_cast(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(i * 4), + value, + buf[i]); + } + } + } + + addr += static_cast(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; +} diff --git a/cores/wb/jtag_wb_bridbe/tool/test.py b/cores/wb/jtag_wb_bridbe/tool/test.py new file mode 100644 index 0000000..264e276 --- /dev/null +++ b/cores/wb/jtag_wb_bridbe/tool/test.py @@ -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) \ No newline at end of file diff --git a/cores/wb/wb_gpio/formal/formal_wb_gpio.v b/cores/wb/wb_gpio/formal/formal_wb_gpio.v new file mode 100644 index 0000000..5a4fdff --- /dev/null +++ b/cores/wb/wb_gpio/formal/formal_wb_gpio.v @@ -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 diff --git a/cores/wb/wb_gpio/formal/wb_gpio.sby b/cores/wb/wb_gpio/formal/wb_gpio.sby new file mode 100644 index 0000000..0fbf6d2 --- /dev/null +++ b/cores/wb/wb_gpio/formal/wb_gpio.sby @@ -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}} diff --git a/cores/wb/wb_gpio/formal/wb_gpio/config.sby b/cores/wb/wb_gpio/formal/wb_gpio/config.sby new file mode 100644 index 0000000..0fbf6d2 --- /dev/null +++ b/cores/wb/wb_gpio/formal/wb_gpio/config.sby @@ -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}} diff --git a/cores/wb/wb_gpio/formal/wb_gpio/logfile.txt b/cores/wb/wb_gpio/formal/wb_gpio/logfile.txt new file mode 100644 index 0000000..5bc85e5 --- /dev/null +++ b/cores/wb/wb_gpio/formal/wb_gpio/logfile.txt @@ -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}}'. diff --git a/cores/wb/wb_gpio/rtl/wb_gpio.v b/cores/wb/wb_gpio/rtl/wb_gpio.v new file mode 100644 index 0000000..da8da52 --- /dev/null +++ b/cores/wb/wb_gpio/rtl/wb_gpio.v @@ -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 diff --git a/cores/wb/wb_gpio/wb_gpio.core b/cores/wb/wb_gpio/wb_gpio.core new file mode 100644 index 0000000..c1a4ec1 --- /dev/null +++ b/cores/wb/wb_gpio/wb_gpio.core @@ -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 diff --git a/cores/wb/wb_mem32/formal/formal_wb_mem32.v b/cores/wb/wb_mem32/formal/formal_wb_mem32.v new file mode 100644 index 0000000..afad5d8 --- /dev/null +++ b/cores/wb/wb_mem32/formal/formal_wb_mem32.v @@ -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 diff --git a/cores/wb/wb_mem32/formal/wb_mem32.sby b/cores/wb/wb_mem32/formal/wb_mem32.sby new file mode 100644 index 0000000..2663fc0 --- /dev/null +++ b/cores/wb/wb_mem32/formal/wb_mem32.sby @@ -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}} diff --git a/cores/wb/wb_mem32/rtl/wb_mem32.v b/cores/wb/wb_mem32/rtl/wb_mem32.v new file mode 100644 index 0000000..9a7aaac --- /dev/null +++ b/cores/wb/wb_mem32/rtl/wb_mem32.v @@ -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 diff --git a/cores/wb/wb_mem32/tb/tb_wb_mem32.v b/cores/wb/wb_mem32/tb/tb_wb_mem32.v new file mode 100644 index 0000000..6971044 --- /dev/null +++ b/cores/wb/wb_mem32/tb/tb_wb_mem32.v @@ -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 diff --git a/cores/wb/wb_mem32/wb_mem32.core b/cores/wb/wb_mem32/wb_mem32.core new file mode 100644 index 0000000..877f97d --- /dev/null +++ b/cores/wb/wb_mem32/wb_mem32.core @@ -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 From 907f244b248d56b0e3ea893683cb71f24250fc8f Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sat, 28 Feb 2026 18:39:50 +0100 Subject: [PATCH 03/12] Added libjtag_wb_bridge --- .../formal/formal_jtag_wb_bridge.v | 0 .../formal/jtag_wb_bridge.sby | 0 .../formal/stub_cdc_req_resp.v | 0 .../formal/stub_jtag_if.v | 0 .../jtag_wb_bridge.core | 0 .../rtl/jtag_wb_bridge.v | 0 .../tool/libjtag_wb_bridge/.gitignore | 0 .../tool/libjtag_wb_bridge/Makefile | 0 .../tool/libjtag_wb_bridge/__init__.py | 0 .../tool/libjtag_wb_bridge/argparse.cpp | 0 .../tool/libjtag_wb_bridge/argparse.hpp | 0 .../tool/libjtag_wb_bridge/difilent_jtag.cpp | 0 .../tool/libjtag_wb_bridge/digilent_jtag.hpp | 0 .../tool/libjtag_wb_bridge/jtag_bridge.py | 14 +++++++++----- .../tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp | 6 +++--- .../tool/libjtag_wb_bridge/jtag_wb_bridge_c.h | 2 +- .../libjtag_wb_bridge/jtag_wb_bridge_client.cpp | 11 ++++++++--- .../libjtag_wb_bridge/jtag_wb_bridge_client.hpp | 2 +- .../tool/libjtag_wb_bridge/prog.cpp | 2 +- .../tool/test.py | 2 +- 20 files changed, 24 insertions(+), 15 deletions(-) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/formal/formal_jtag_wb_bridge.v (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/formal/jtag_wb_bridge.sby (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/formal/stub_cdc_req_resp.v (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/formal/stub_jtag_if.v (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/jtag_wb_bridge.core (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/rtl/jtag_wb_bridge.v (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/.gitignore (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/Makefile (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/__init__.py (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/argparse.cpp (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/argparse.hpp (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/difilent_jtag.cpp (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/digilent_jtag.hpp (100%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/jtag_bridge.py (92%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp (94%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h (94%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp (93%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp (95%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/libjtag_wb_bridge/prog.cpp (98%) rename cores/wb/{jtag_wb_bridbe => jtag_wb_bridge}/tool/test.py (83%) diff --git a/cores/wb/jtag_wb_bridbe/formal/formal_jtag_wb_bridge.v b/cores/wb/jtag_wb_bridge/formal/formal_jtag_wb_bridge.v similarity index 100% rename from cores/wb/jtag_wb_bridbe/formal/formal_jtag_wb_bridge.v rename to cores/wb/jtag_wb_bridge/formal/formal_jtag_wb_bridge.v diff --git a/cores/wb/jtag_wb_bridbe/formal/jtag_wb_bridge.sby b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby similarity index 100% rename from cores/wb/jtag_wb_bridbe/formal/jtag_wb_bridge.sby rename to cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby diff --git a/cores/wb/jtag_wb_bridbe/formal/stub_cdc_req_resp.v b/cores/wb/jtag_wb_bridge/formal/stub_cdc_req_resp.v similarity index 100% rename from cores/wb/jtag_wb_bridbe/formal/stub_cdc_req_resp.v rename to cores/wb/jtag_wb_bridge/formal/stub_cdc_req_resp.v diff --git a/cores/wb/jtag_wb_bridbe/formal/stub_jtag_if.v b/cores/wb/jtag_wb_bridge/formal/stub_jtag_if.v similarity index 100% rename from cores/wb/jtag_wb_bridbe/formal/stub_jtag_if.v rename to cores/wb/jtag_wb_bridge/formal/stub_jtag_if.v diff --git a/cores/wb/jtag_wb_bridbe/jtag_wb_bridge.core b/cores/wb/jtag_wb_bridge/jtag_wb_bridge.core similarity index 100% rename from cores/wb/jtag_wb_bridbe/jtag_wb_bridge.core rename to cores/wb/jtag_wb_bridge/jtag_wb_bridge.core diff --git a/cores/wb/jtag_wb_bridbe/rtl/jtag_wb_bridge.v b/cores/wb/jtag_wb_bridge/rtl/jtag_wb_bridge.v similarity index 100% rename from cores/wb/jtag_wb_bridbe/rtl/jtag_wb_bridge.v rename to cores/wb/jtag_wb_bridge/rtl/jtag_wb_bridge.v diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/.gitignore b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/.gitignore similarity index 100% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/.gitignore rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/.gitignore diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/Makefile b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/Makefile similarity index 100% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/Makefile rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/Makefile diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/__init__.py b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/__init__.py similarity index 100% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/__init__.py rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/__init__.py diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.cpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/argparse.cpp similarity index 100% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.cpp rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/argparse.cpp diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.hpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/argparse.hpp similarity index 100% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/argparse.hpp rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/argparse.hpp diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/difilent_jtag.cpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/difilent_jtag.cpp similarity index 100% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/difilent_jtag.cpp rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/difilent_jtag.cpp diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/digilent_jtag.hpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/digilent_jtag.hpp similarity index 100% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/digilent_jtag.hpp rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/digilent_jtag.hpp diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_bridge.py b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_bridge.py similarity index 92% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_bridge.py rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_bridge.py index 70c435c..90fc03a 100644 --- a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_bridge.py +++ b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_bridge.py @@ -49,7 +49,7 @@ class JtagBridge: 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.argtypes = [ctypes.c_void_p] 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 @@ -94,7 +94,12 @@ class JtagBridge: def _check(self, ok): if not ok: message = self._lib.jtag_bridge_last_error(self._handle) - raise JtagBridgeError(message.decode("utf-8")) + if not message: + raise JtagBridgeError("operation failed") + decoded = message.decode("utf-8", errors="replace") + if not decoded: + decoded = "operation failed" + raise JtagBridgeError(decoded) def open(self, port=0, chain=1): self._check(self._lib.jtag_bridge_open(self._handle, port, chain)) @@ -118,9 +123,8 @@ class JtagBridge: 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 + self._check(self._lib.jtag_bridge_ping(self._handle)) + return True def set_reset(self, enabled): self._check(self._lib.jtag_bridge_set_reset(self._handle, int(bool(enabled)))) diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp similarity index 94% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp index 71812ea..fd1a1ff 100644 --- a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp +++ b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_c.cpp @@ -67,9 +67,9 @@ int jtag_bridge_clear_flags(JtagBridgeHandle* handle) { }); } -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_ping(JtagBridgeHandle* handle) { + return callBridge(handle, [](JtagWishboneBridge& bridge) { + return bridge.ping(); }); } diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h similarity index 94% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h index a14829c..e4f89d7 100644 --- a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h +++ b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_c.h @@ -19,7 +19,7 @@ int jtag_bridge_set_speed(JtagBridgeHandle* handle, uint32_t requested_hz, uint3 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_ping(JtagBridgeHandle* handle); int jtag_bridge_set_reset(JtagBridgeHandle* handle, int enabled); int jtag_bridge_write8(JtagBridgeHandle* handle, uint32_t addr, uint8_t value); diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp similarity index 93% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp index c3df11b..b657a6b 100644 --- a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp +++ b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp @@ -98,14 +98,19 @@ bool JtagWishboneBridge::clearFlags() { return executeCommand(kOpClearFlags, 0, 0, nullptr); } -bool JtagWishboneBridge::ping(uint8_t* ping_value) { +bool JtagWishboneBridge::ping() { uint32_t response = 0; + uint8_t ping_value; if (!executeCommand(kOpPing, 0, 0, &response)) { return false; } - if (ping_value) { - *ping_value = static_cast(response & 0xffu); + ping_value = static_cast(response & 0xffu); + if (ping_value != 0xa5) { + char msg[96]; + std::snprintf(msg, sizeof(msg), "ping mismatch: expected 0xa4, got 0x%02x", ping_value); + return setError(msg); } + last_error_.clear(); return true; } diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp similarity index 95% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp index 53679b7..2bf3968 100644 --- a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp +++ b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.hpp @@ -23,7 +23,7 @@ public: bool setChain(int chain); bool clearFlags(); - bool ping(uint8_t* ping_value = nullptr); + bool ping(); bool setReset(bool enabled); bool write8(uint32_t addr, uint8_t value); diff --git a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/prog.cpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/prog.cpp similarity index 98% rename from cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/prog.cpp rename to cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/prog.cpp index 7be3c58..a25ac0e 100644 --- a/cores/wb/jtag_wb_bridbe/tool/libjtag_wb_bridge/prog.cpp +++ b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/prog.cpp @@ -32,7 +32,7 @@ int main(int argc, char** argv) { } uint8_t ping_value = 0; - if (!bridge.ping(&ping_value)) { + if (!bridge.ping()) { std::printf("PING command failed: %s\n", bridge.lastError().c_str()); return -1; } diff --git a/cores/wb/jtag_wb_bridbe/tool/test.py b/cores/wb/jtag_wb_bridge/tool/test.py similarity index 83% rename from cores/wb/jtag_wb_bridbe/tool/test.py rename to cores/wb/jtag_wb_bridge/tool/test.py index 264e276..1f298f2 100644 --- a/cores/wb/jtag_wb_bridbe/tool/test.py +++ b/cores/wb/jtag_wb_bridge/tool/test.py @@ -3,6 +3,6 @@ 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.ping() bridge.write32(0x0, 0xAA) \ No newline at end of file From cf483decad8d7e77ff0cbb23d882557f85050544 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sat, 28 Feb 2026 21:46:59 +0100 Subject: [PATCH 04/12] Added everything from the other system --- .gitmodules | 6 + cores/system/mcu/mcu.core | 48 +++ cores/system/mcu/rtl/mcu.v | 382 ++++++++++++++++++ cores/system/mcu/rtl/mcu_peripherals.v | 126 ++++++ cores/system/test/rtl/toplevel.v | 110 +++-- cores/system/test/sw/.gitignore | 8 + cores/system/test/sw/sweep/Makefile | 47 +++ cores/system/test/sw/sweep/link.ld | 35 ++ cores/system/test/sw/sweep/start.s | 99 +++++ cores/system/test/sw/sweep/sweep.c | 45 +++ cores/system/test/test.core | 11 +- cores/util/clog2/clog2.vh | 2 + cores/util/conv/conv.core | 16 + cores/util/conv/conv.vh | 16 + .../formal/formal_wb_master_checker.v | 1 + .../jtag_wb_bridge/formal/jtag_wb_bridge.sby | 2 +- cores/wb/jtag_wb_bridge/rtl/jtag_wb_bridge.v | 4 +- .../tool/libjtag_wb_bridge/Makefile | 2 +- .../jtag_wb_bridge_client.cpp | 2 +- .../tool/libjtag_wb_bridge/prog.cpp | 5 - cores/wb/jtag_wb_bridge/tool/test.py | 2 +- cores/wb/wb_arbiter/rtl/arbiter.v | 138 +++++++ cores/wb/wb_arbiter/rtl/wb_arbiter.v | 101 +++++ cores/wb/wb_arbiter/wb_arbiter.core | 42 ++ cores/wb/wb_gpio/wb_gpio.core | 2 + cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v | 63 +++ cores/wb/wb_gpio_banks/wb_gpio_banks.core | 31 ++ cores/wb/wb_mux/rtl/wb_mux.v | 145 +++++++ cores/wb/wb_mux/wb_mux.core | 51 +++ cores/wb/wb_timer/rtl/wb_timer.v | 74 ++++ cores/wb/wb_timer/wb_timer.core | 29 ++ fusesoc.conf | 11 + fusesoc_libraries/fusesoc-cores | 1 + fusesoc_libraries/serv | 1 + 34 files changed, 1601 insertions(+), 57 deletions(-) create mode 100644 .gitmodules create mode 100644 cores/system/mcu/mcu.core create mode 100644 cores/system/mcu/rtl/mcu.v create mode 100644 cores/system/mcu/rtl/mcu_peripherals.v create mode 100644 cores/system/test/sw/.gitignore create mode 100644 cores/system/test/sw/sweep/Makefile create mode 100644 cores/system/test/sw/sweep/link.ld create mode 100644 cores/system/test/sw/sweep/start.s create mode 100644 cores/system/test/sw/sweep/sweep.c create mode 100644 cores/util/conv/conv.core create mode 100644 cores/util/conv/conv.vh create mode 100644 cores/wb/wb_arbiter/rtl/arbiter.v create mode 100644 cores/wb/wb_arbiter/rtl/wb_arbiter.v create mode 100644 cores/wb/wb_arbiter/wb_arbiter.core create mode 100644 cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v create mode 100644 cores/wb/wb_gpio_banks/wb_gpio_banks.core create mode 100644 cores/wb/wb_mux/rtl/wb_mux.v create mode 100644 cores/wb/wb_mux/wb_mux.core create mode 100644 cores/wb/wb_timer/rtl/wb_timer.v create mode 100644 cores/wb/wb_timer/wb_timer.core create mode 160000 fusesoc_libraries/fusesoc-cores create mode 160000 fusesoc_libraries/serv diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9552b04 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "fusesoc_libraries/serv"] + path = fusesoc_libraries/serv + url = https://github.com/Jojojoppe/serv/tree/xilinx_ise +[submodule "fusesoc_libraries/fusesoc-cores"] + path = fusesoc_libraries/fusesoc-cores + url = https://github.com/fusesoc/fusesoc-cores diff --git a/cores/system/mcu/mcu.core b/cores/system/mcu/mcu.core new file mode 100644 index 0000000..4839bad --- /dev/null +++ b/cores/system/mcu/mcu.core @@ -0,0 +1,48 @@ +CAPI=2: + +name: joppeb:system:mcu:1.0 +description: basic RISC-V MCU system + +filesets: + rtl: + depend: + - "^award-winning:serv:servile:1.4.0" + - joppeb:util:clog2 + - joppeb:wb:jtag_wb_bridge + - joppeb:wb:wb_gpio_banks + - joppeb:wb:wb_timer + - joppeb:wb:wb_arbiter + - joppeb:wb:wb_mux + files: + - rtl/mcu.v + - rtl/mcu_peripherals.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: mcu + parameters: + - memfile + - memsize + - sim + - jtag + +parameters: + memfile: + datatype: str + description: Memory initialization file passed to the internal RAM + paramtype: vlogparam + memsize: + datatype: int + description: Internal RAM size in bytes + paramtype: vlogparam + sim: + datatype: int + description: Enable simulation-friendly RAM initialization behavior + paramtype: vlogparam + jtag: + datatype: int + description: Enable the JTAG Wishbone bridge and arbiter path + paramtype: vlogparam diff --git a/cores/system/mcu/rtl/mcu.v b/cores/system/mcu/rtl/mcu.v new file mode 100644 index 0000000..16f094f --- /dev/null +++ b/cores/system/mcu/rtl/mcu.v @@ -0,0 +1,382 @@ +`timescale 1ns/1ps +`include "clog2.vh" + +module mcu #( + parameter memfile = "", + parameter memsize = 8192, + parameter sim = 1'b0, + parameter jtag = 1 +)( + input wire i_clk, + input wire i_rst, + + input wire [31:0] i_GPI_A, + input wire [31:0] i_GPI_B, + input wire [31:0] i_GPI_C, + input wire [31:0] i_GPI_D, + output wire [31:0] o_GPO_A, + output wire [31:0] o_GPO_B, + output wire [31:0] o_GPO_C, + output wire [31:0] o_GPO_D +); + localparam WITH_CSR = 1; + localparam rf_width = 8; + + wire rst; + wire rst_wb; + wire rst_mem_peripherals; + wire rst_cmd_jtag; + wire timer_irq; + assign rst = i_rst | rst_mem_peripherals | rst_cmd_jtag; + // Keep the Wishbone path alive during JTAG "core reset" so memory can be programmed. + assign rst_wb = i_rst; + + // Busses + // CPU<->memory interconnect (CPU is a WB master) + wire [31:0] wb_mem_adr; + wire [31:0] wb_mem_dat; + wire [3:0] wb_mem_sel; + wire wb_mem_we; + wire wb_mem_stb; + wire [31:0] wb_mem_rdt_cpu; + wire wb_mem_ack_cpu; + + // Interconnect->memory (shared WB slave side) + wire [31:0] wb_mem_adr_s; + wire [31:0] wb_mem_dat_s; + wire [3:0] wb_mem_sel_s; + wire wb_mem_we_s; + wire wb_mem_stb_s; + wire [31:0] wb_mem_rdt_s; + wire wb_mem_ack_s; + + // CPU->peripherals + wire [31:0] wb_ext_adr; + wire [31:0] wb_ext_dat; + wire [3:0] wb_ext_sel; + wire wb_ext_we; + wire wb_ext_stb; + wire [31:0] wb_ext_rdt; + wire wb_ext_ack; + + // GPIO + wire [4*32-1:0] GPO; + wire [4*32-1:0] GPI; + assign o_GPO_A = GPO[32*1-1:32*0]; + assign o_GPO_B = GPO[32*2-1:32*1]; + assign o_GPO_C = GPO[32*3-1:32*2]; + assign o_GPO_D = GPO[32*4-1:32*3]; + assign GPI[32*1-1:32*0] = i_GPI_A; + assign GPI[32*2-1:32*1] = i_GPI_B; + assign GPI[32*3-1:32*2] = i_GPI_C; + assign GPI[32*4-1:32*3] = i_GPI_D; + + cpu #( + .sim(sim), + .WITH_CSR(WITH_CSR), + .rf_width(rf_width) + ) cpu ( + .i_clk(i_clk), + .i_rst(rst), + .i_timer_irq(timer_irq), + + //Memory interface + .o_wb_mem_adr(wb_mem_adr), + .o_wb_mem_dat(wb_mem_dat), + .o_wb_mem_sel(wb_mem_sel), + .o_wb_mem_we(wb_mem_we), + .o_wb_mem_stb(wb_mem_stb), + .i_wb_mem_rdt(wb_mem_rdt_cpu), + .i_wb_mem_ack(wb_mem_ack_cpu), + + //Extension interface + .o_wb_ext_adr(wb_ext_adr), + .o_wb_ext_dat(wb_ext_dat), + .o_wb_ext_sel(wb_ext_sel), + .o_wb_ext_we(wb_ext_we), + .o_wb_ext_stb(wb_ext_stb), + .i_wb_ext_rdt(wb_ext_rdt), + .i_wb_ext_ack(wb_ext_ack) + ); + + generate + if (jtag) begin : gen_jtag_wb + wire [31:0] wb_jtag_adr; + wire [31:0] wb_jtag_dat; + wire [3:0] wb_jtag_sel; + wire wb_jtag_we; + wire wb_jtag_cyc; + wire wb_jtag_stb; + wire [31:0] wb_jtag_rdt; + wire wb_jtag_ack; + + wire [2*32-1:0] wbm_adr_i; + wire [2*32-1:0] wbm_dat_i; + wire [2*4-1:0] wbm_sel_i; + wire [1:0] wbm_we_i; + wire [1:0] wbm_cyc_i; + wire [1:0] wbm_stb_i; + wire [2*3-1:0] wbm_cti_i; + wire [2*2-1:0] wbm_bte_i; + wire [2*32-1:0] wbm_dat_o; + wire [1:0] wbm_ack_o; + wire [1:0] wbm_err_o; + wire [1:0] wbm_rty_o; + + assign wbm_adr_i = {wb_jtag_adr, wb_mem_adr}; + assign wbm_dat_i = {wb_jtag_dat, wb_mem_dat}; + assign wbm_sel_i = {wb_jtag_sel, wb_mem_sel}; + assign wbm_we_i = {wb_jtag_we, wb_mem_we}; + assign wbm_cyc_i = {wb_jtag_cyc, wb_mem_stb}; + assign wbm_stb_i = {wb_jtag_stb, wb_mem_stb}; + assign wbm_cti_i = 6'b0; + assign wbm_bte_i = 4'b0; + + assign wb_mem_rdt_cpu = wbm_dat_o[31:0]; + assign wb_mem_ack_cpu = wbm_ack_o[0]; + assign wb_jtag_rdt = wbm_dat_o[63:32]; + assign wb_jtag_ack = wbm_ack_o[1]; + + wb_arbiter #( + .dw(32), + .aw(32), + .num_masters(2) + ) wb_mem_arbiter ( + .wb_clk_i(i_clk), + .wb_rst_i(rst_wb), + .wbm_adr_i(wbm_adr_i), + .wbm_dat_i(wbm_dat_i), + .wbm_sel_i(wbm_sel_i), + .wbm_we_i(wbm_we_i), + .wbm_cyc_i(wbm_cyc_i), + .wbm_stb_i(wbm_stb_i), + .wbm_cti_i(wbm_cti_i), + .wbm_bte_i(wbm_bte_i), + .wbm_dat_o(wbm_dat_o), + .wbm_ack_o(wbm_ack_o), + .wbm_err_o(wbm_err_o), + .wbm_rty_o(wbm_rty_o), + .wbs_adr_o(wb_mem_adr_s), + .wbs_dat_o(wb_mem_dat_s), + .wbs_sel_o(wb_mem_sel_s), + .wbs_we_o(wb_mem_we_s), + .wbs_cyc_o(), + .wbs_stb_o(wb_mem_stb_s), + .wbs_cti_o(), + .wbs_bte_o(), + .wbs_dat_i(wb_mem_rdt_s), + .wbs_ack_i(wb_mem_ack_s), + .wbs_err_i(1'b0), + .wbs_rty_i(1'b0) + ); + + jtag_wb_bridge #( + .chain(1) + ) jtag_wb ( + .i_clk(i_clk), + .i_rst(i_rst), + .o_wb_adr(wb_jtag_adr), + .o_wb_dat(wb_jtag_dat), + .o_wb_sel(wb_jtag_sel), + .o_wb_we(wb_jtag_we), + .o_wb_cyc(wb_jtag_cyc), + .o_wb_stb(wb_jtag_stb), + .i_wb_rdt(wb_jtag_rdt), + .i_wb_ack(wb_jtag_ack), + .o_cmd_reset(rst_cmd_jtag) + ); + end else begin : gen_no_jtag_wb + assign wb_mem_adr_s = wb_mem_adr; + assign wb_mem_dat_s = wb_mem_dat; + assign wb_mem_sel_s = wb_mem_sel; + assign wb_mem_we_s = wb_mem_we; + assign wb_mem_stb_s = wb_mem_stb; + assign wb_mem_rdt_cpu = wb_mem_rdt_s; + assign wb_mem_ack_cpu = wb_mem_ack_s; + assign rst_cmd_jtag = 1'b0; + end + endgenerate + + memory #( + .memfile(memfile), + .memsize(memsize), + .sim(sim) + ) memory ( + .i_clk(i_clk), + .i_rst(i_rst), + .i_wb_rst(rst_wb), + .i_wb_adr(wb_mem_adr_s), + .i_wb_dat(wb_mem_dat_s), + .i_wb_sel(wb_mem_sel_s), + .i_wb_we(wb_mem_we_s), + .i_wb_stb(wb_mem_stb_s), + .o_wb_rdt(wb_mem_rdt_s), + .o_wb_ack(wb_mem_ack_s) + ); + + mcu_peripherals peripherals ( + .i_clk(i_clk), + .i_rst(rst), + .i_wb_adr(wb_ext_adr), + .i_wb_dat(wb_ext_dat), + .i_wb_sel(wb_ext_sel), + .i_wb_we(wb_ext_we), + .i_wb_stb(wb_ext_stb), + .o_wb_rdt(wb_ext_rdt), + .o_wb_ack(wb_ext_ack), + // Peripheral IO + .i_gpio(GPI), + .o_gpio(GPO), + .o_timer_irq(timer_irq), + .o_core_reset(rst_mem_peripherals) + ); + +endmodule + +module cpu #( + parameter sim = 1'b0, + parameter WITH_CSR = 1, + parameter rf_width = 8 +)( + input wire i_clk, + input wire i_rst, + input wire i_timer_irq, + // CPU->memory + output wire [31:0] o_wb_mem_adr, + output wire [31:0] o_wb_mem_dat, + output wire [3:0] o_wb_mem_sel, + output wire o_wb_mem_we, + output wire o_wb_mem_stb, + input wire [31:0] i_wb_mem_rdt, + input wire i_wb_mem_ack, + // CPU->peripherals + output wire [31:0] o_wb_ext_adr, + output wire [31:0] o_wb_ext_dat, + output wire [3:0] o_wb_ext_sel, + output wire o_wb_ext_we, + output wire o_wb_ext_stb, + input wire [31:0] i_wb_ext_rdt, + input wire i_wb_ext_ack +); + wire [6+WITH_CSR:0] rf_waddr; + wire [rf_width-1:0] rf_wdata; + wire rf_wen; + wire [6+WITH_CSR:0] rf_raddr; + wire [rf_width-1:0] rf_rdata; + wire rf_ren; + + // SERV core with mux splitting dbus into mem and ext and + // arbiter combining mem and ibus. + servile #( + .reset_pc(32'h0000_0000), + .reset_strategy("MINI"), + .rf_width(rf_width), + .sim(sim), + .with_csr(WITH_CSR), + .with_c(0), + .with_mdu(0) + ) servile ( + .i_clk(i_clk), + .i_rst(i_rst), + .i_timer_irq(i_timer_irq), + + .o_wb_mem_adr(o_wb_mem_adr), + .o_wb_mem_dat(o_wb_mem_dat), + .o_wb_mem_sel(o_wb_mem_sel), + .o_wb_mem_we(o_wb_mem_we), + .o_wb_mem_stb(o_wb_mem_stb), + .i_wb_mem_rdt(i_wb_mem_rdt), + .i_wb_mem_ack(i_wb_mem_ack), + + .o_wb_ext_adr(o_wb_ext_adr), + .o_wb_ext_dat(o_wb_ext_dat), + .o_wb_ext_sel(o_wb_ext_sel), + .o_wb_ext_we(o_wb_ext_we), + .o_wb_ext_stb(o_wb_ext_stb), + .i_wb_ext_rdt(i_wb_ext_rdt), + .i_wb_ext_ack(i_wb_ext_ack), + + .o_rf_waddr(rf_waddr), + .o_rf_wdata(rf_wdata), + .o_rf_wen(rf_wen), + .o_rf_raddr(rf_raddr), + .o_rf_ren(rf_ren), + .i_rf_rdata(rf_rdata) + ); + + serv_rf_ram #( + .width(rf_width), + .csr_regs(WITH_CSR*4) + ) rf_ram ( + .i_clk(i_clk), + .i_waddr(rf_waddr), + .i_wdata(rf_wdata), + .i_wen(rf_wen), + .i_raddr(rf_raddr), + .i_ren(rf_ren), + .o_rdata(rf_rdata) + ); +endmodule + +module memory #( + 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, + output wire [31:0] o_wb_rdt, + output wire o_wb_ack +); + localparam mem_depth = memsize/4; + localparam mem_aw = `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 & ~wb_ack_r; + + if (i_wb_stb & ~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 diff --git a/cores/system/mcu/rtl/mcu_peripherals.v b/cores/system/mcu/rtl/mcu_peripherals.v new file mode 100644 index 0000000..771445e --- /dev/null +++ b/cores/system/mcu/rtl/mcu_peripherals.v @@ -0,0 +1,126 @@ +`timescale 1ns/1ps + +module mcu_peripherals ( + input wire i_clk, + input wire i_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, + output wire [31:0] o_wb_rdt, + output wire o_wb_ack, + + input wire [4*32-1:0] i_gpio, + output wire [4*32-1:0] o_gpio, + output wire o_timer_irq, + output wire o_core_reset +); + localparam [31:0] GPIO_BASE_ADDR = 32'h4000_0000; + localparam [31:0] GPIO_ADDR_MASK = 32'hFFFF_0000; + localparam [31:0] TIMER_BASE_ADDR = 32'h4001_0000; + localparam [31:0] TIMER_ADDR_MASK = 32'hFFFF_0000; + + assign o_core_reset = 1'b0; + + wire [2*32-1:0] wbs_adr; + wire [2*32-1:0] wbs_dat_w; + wire [2*4-1:0] wbs_sel; + wire [1:0] wbs_we; + wire [1:0] wbs_cyc; + wire [1:0] wbs_stb; + wire [2*3-1:0] wbs_cti; + wire [2*2-1:0] wbs_bte; + wire [2*32-1:0] wbs_dat_r; + wire [1:0] wbs_ack; + + wire [31:0] gpio_wbs_adr = wbs_adr[0*32 +: 32]; + wire [31:0] gpio_wbs_dat_w = wbs_dat_w[0*32 +: 32]; + wire [3:0] gpio_wbs_sel = wbs_sel[0*4 +: 4]; + wire gpio_wbs_we = wbs_we[0]; + wire gpio_wbs_cyc = wbs_cyc[0]; + wire gpio_wbs_stb = wbs_stb[0]; + wire [31:0] gpio_wbs_dat_r; + wire gpio_wbs_ack; + + wire [31:0] timer_wbs_dat_w = wbs_dat_w[1*32 +: 32]; + wire timer_wbs_we = wbs_we[1]; + wire timer_wbs_cyc = wbs_cyc[1]; + wire timer_wbs_stb = wbs_stb[1]; + wire [31:0] timer_wbs_dat_r; + wire timer_wbs_ack; + + wb_mux #( + .dw(32), + .aw(32), + .num_slaves(2), + .MATCH_ADDR({TIMER_BASE_ADDR, GPIO_BASE_ADDR}), + .MATCH_MASK({TIMER_ADDR_MASK, GPIO_ADDR_MASK}) + ) ext_mux ( + .wb_clk_i(i_clk), + .wb_rst_i(i_rst), + + .wbm_adr_i(i_wb_adr), + .wbm_dat_i(i_wb_dat), + .wbm_sel_i(i_wb_sel), + .wbm_we_i(i_wb_we), + .wbm_cyc_i(i_wb_stb), + .wbm_stb_i(i_wb_stb), + .wbm_cti_i(3'b000), + .wbm_bte_i(2'b00), + .wbm_dat_o(o_wb_rdt), + .wbm_ack_o(o_wb_ack), + .wbm_err_o(), + .wbm_rty_o(), + + .wbs_adr_o(wbs_adr), + .wbs_dat_o(wbs_dat_w), + .wbs_sel_o(wbs_sel), + .wbs_we_o(wbs_we), + .wbs_cyc_o(wbs_cyc), + .wbs_stb_o(wbs_stb), + .wbs_cti_o(wbs_cti), + .wbs_bte_o(wbs_bte), + .wbs_dat_i(wbs_dat_r), + .wbs_ack_i(wbs_ack), + .wbs_err_i(2'b00), + .wbs_rty_i(2'b00) + ); + + wb_gpio_banks #( + .BASE_ADDR(GPIO_BASE_ADDR), + .NUM_BANKS(4) + ) gpio ( + .i_wb_clk(i_clk), + .i_wb_rst(i_rst), + .i_wb_dat(gpio_wbs_dat_w), + .i_wb_adr(gpio_wbs_adr), + .i_wb_we(gpio_wbs_we), + .i_wb_stb(gpio_wbs_stb & gpio_wbs_cyc), + .i_wb_sel(gpio_wbs_sel), + .o_wb_rdt(gpio_wbs_dat_r), + .o_wb_ack(gpio_wbs_ack), + .i_gpio(i_gpio), + .o_gpio(o_gpio) + ); + + assign wbs_dat_r[0*32 +: 32] = gpio_wbs_dat_r; + assign wbs_ack[0] = gpio_wbs_ack; + + wb_countdown_timer timer ( + .i_clk(i_clk), + .i_rst(i_rst), + .o_irq(o_timer_irq), + .i_wb_dat(timer_wbs_dat_w), + .o_wb_dat(timer_wbs_dat_r), + .i_wb_we(timer_wbs_we), + .i_wb_cyc(timer_wbs_cyc), + .i_wb_stb(timer_wbs_stb), + .o_wb_ack(timer_wbs_ack) + ); + + + assign wbs_dat_r[1*32 +: 32] = timer_wbs_dat_r; + assign wbs_ack[1] = timer_wbs_ack; +endmodule diff --git a/cores/system/test/rtl/toplevel.v b/cores/system/test/rtl/toplevel.v index 9a7e348..093a97d 100644 --- a/cores/system/test/rtl/toplevel.v +++ b/cores/system/test/rtl/toplevel.v @@ -1,6 +1,8 @@ `timescale 1ns/1ps -module toplevel( +module toplevel #( + parameter sim = 0 +)( input wire aclk, input wire aresetn, @@ -11,6 +13,7 @@ module toplevel( output wire[7:0] LED ); + `include "conv.vh" // Clocking wire clk_100; @@ -25,56 +28,75 @@ module toplevel( .clk_out(clk_15) ); - wire wb_rst; - assign wb_rst = ~aresetn; - wire [31:0] wb_adr; - wire [31:0] wb_dat_w; - wire [31:0] wb_dat_r; - wire [3:0] wb_sel; - wire wb_we; - wire wb_cyc; - wire wb_stb; - wire wb_ack; - wire wb_cmd_reset; + // Reset conditioning for button input: + // - asynchronous assert when button is pressed (aresetn=0) + // - synchronous, debounced deassert in clk_15 domain + localparam [17:0] RESET_RELEASE_CYCLES = sim ? 18'd16 : 18'd150000; // ~10 ms @ 15 MHz on hardware + reg [17:0] rst_cnt = 18'd0; + reg sys_reset_r = 1'b1; + always @(posedge clk_15 or negedge aresetn) begin + if (!aresetn) begin + rst_cnt <= 18'd0; + sys_reset_r <= 1'b1; + end else if (sys_reset_r) begin + if (rst_cnt == RESET_RELEASE_CYCLES - 1'b1) + sys_reset_r <= 1'b0; + else + rst_cnt <= rst_cnt + 1'b1; + end + end + wire sys_reset = sys_reset_r; + wire sys_resetn = !sys_reset_r; - wire [31:0] gpio_out; - wire gpio_rst; - assign gpio_rst = wb_rst; + wire [31:0] GPIO_A; + wire [31:0] GPIO_B; + wire [31:0] GPIO_C; + wire [31:0] GPIO_D; - jtag_wb_bridge u_jtag_wb_bridge ( + wire test; + + mcu #( + .memfile("../sw/sweep/sweep.hex"), + .sim(sim), + .jtag(1) + ) mcu ( .i_clk(clk_15), - .i_rst(wb_rst), - .o_wb_adr(wb_adr), - .o_wb_dat(wb_dat_w), - .o_wb_sel(wb_sel), - .o_wb_we(wb_we), - .o_wb_cyc(wb_cyc), - .o_wb_stb(wb_stb), - .i_wb_rdt(wb_dat_r), - .i_wb_ack(wb_ack), - .o_cmd_reset(wb_cmd_reset) + .i_rst(sys_reset), + .i_GPI_A(GPIO_A), + .i_GPI_B(GPIO_B), + .i_GPI_C(GPIO_C), + .i_GPI_D(GPIO_D), + .o_GPO_A(GPIO_A), + .o_GPO_B(GPIO_B), + .o_GPO_C(GPIO_C), + .o_GPO_D(GPIO_D) ); - wb_gpio #( - .address(32'h00000000) - ) u_wb_gpio ( - .i_wb_clk(clk_15), - .i_wb_rst(gpio_rst), - .i_wb_adr(wb_adr), - .i_wb_dat(wb_dat_w), - .i_wb_sel(wb_sel), - .i_wb_we(wb_we), - .i_wb_stb(wb_cyc & wb_stb), - .i_gpio(gpio_out), - .o_wb_rdt(wb_dat_r), - .o_wb_ack(wb_ack), - .o_gpio(gpio_out) + + wire [15:0] sin_q15; + wire clk_en; + nco_q15 #( + .CLK_HZ(15_000_000), + .FS_HZ(80_000) + ) nco ( + .clk (clk_15), + .rst_n (sys_resetn), + .freq_hz(GPIO_A), + .sin_q15(sin_q15), + .cos_q15(), + .clk_en (clk_en) ); - assign led_green = aresetn; - assign led_red = wb_cmd_reset; - assign LED = gpio_out[7:0]; - assign r2r = gpio_out[13:8]; + reg [5:0] dac_code; + always @(posedge clk_15) begin + dac_code <= q15_to_uq16(sin_q15) >> 10; + end + assign r2r = dac_code; + + assign LED = GPIO_B[7:0]; + assign led_green = GPIO_C[0]; + assign led_red = GPIO_C[1]; + endmodule diff --git a/cores/system/test/sw/.gitignore b/cores/system/test/sw/.gitignore new file mode 100644 index 0000000..033d388 --- /dev/null +++ b/cores/system/test/sw/.gitignore @@ -0,0 +1,8 @@ +*.o +*.hex +*.bin +*.map +*.elf.asm +*.elf +*.coe +*.mif \ No newline at end of file diff --git a/cores/system/test/sw/sweep/Makefile b/cores/system/test/sw/sweep/Makefile new file mode 100644 index 0000000..5e1cd7d --- /dev/null +++ b/cores/system/test/sw/sweep/Makefile @@ -0,0 +1,47 @@ +TOOLCHAIN_PREFIX ?= riscv64-elf- + +CC := $(TOOLCHAIN_PREFIX)gcc +OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy +OBJDUMP := $(TOOLCHAIN_PREFIX)objdump +SIZE := $(TOOLCHAIN_PREFIX)size + +TARGET := sweep +SRCS_C := sweep.c +SRCS_S := start.s +OBJS := $(SRCS_C:.c=.o) $(SRCS_S:.s=.o) + +ARCH_FLAGS := -march=rv32i_zicsr -mabi=ilp32 +CFLAGS := $(ARCH_FLAGS) -Os -ffreestanding -fno-builtin -Wall -Wextra +ASFLAGS := $(ARCH_FLAGS) +LDFLAGS := $(ARCH_FLAGS) -nostdlib -nostartfiles -Wl,-Bstatic,-Tlink.ld,--gc-sections,-Map,$(TARGET).map + +.PHONY: all clean disasm size + +all: $(TARGET).elf $(TARGET).bin $(TARGET).hex $(TARGET).elf.asm + +$(TARGET).elf: $(OBJS) link.ld + $(CC) $(LDFLAGS) -o $@ $(OBJS) + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +%.o: %.s + $(CC) $(ASFLAGS) -c -o $@ $< + +$(TARGET).bin: $(TARGET).elf + $(OBJCOPY) -O binary $< $@ + +$(TARGET).hex: $(TARGET).bin + hexdump -v -e '1/4 "%08x\n"' $< > $@ + +$(TARGET).elf.asm: $(TARGET).elf + $(OBJDUMP) -d -S $< > $@ + +disasm: $(TARGET).elf.asm + +size: $(TARGET).elf + $(SIZE) $< + +clean: + rm -f $(TARGET).elf $(TARGET).bin $(TARGET).hex $(TARGET).coe $(TARGET).mif \ + $(TARGET).elf.asm $(TARGET).map $(OBJS) diff --git a/cores/system/test/sw/sweep/link.ld b/cores/system/test/sw/sweep/link.ld new file mode 100644 index 0000000..b634e26 --- /dev/null +++ b/cores/system/test/sw/sweep/link.ld @@ -0,0 +1,35 @@ +OUTPUT_ARCH("riscv") +ENTRY(_start) + +MEMORY +{ + RAM (rwx) : ORIGIN = 0x00000000, LENGTH = 8192 +} + +SECTIONS +{ + .text : + { + KEEP(*(.text.init)) + *(.text .text.*) + *(.rodata .rodata.*) + } > RAM + + .data : + { + *(.data .data.*) + } > RAM + + .bss (NOLOAD) : + { + __bss_start = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(.scommon) + *(COMMON) + __bss_end = .; + } > RAM + + . = ALIGN(4); + __stack_top = ORIGIN(RAM) + LENGTH(RAM); +} diff --git a/cores/system/test/sw/sweep/start.s b/cores/system/test/sw/sweep/start.s new file mode 100644 index 0000000..2154050 --- /dev/null +++ b/cores/system/test/sw/sweep/start.s @@ -0,0 +1,99 @@ +.section .text.init +.globl _start +.type _start, @function +_start: + la sp, __stack_top + + # Zero .bss + la t0, __bss_start + la t1, __bss_end +1: + bgeu t0, t1, 2f + sw zero, 0(t0) + addi t0, t0, 4 + j 1b + +2: + call main + +3: + j 3b + +.size _start, .-_start + + +.section .text +.globl trap_entry +.type trap_entry, @function +trap_entry: + # Save full integer context (except x0/x2) because an interrupt can + # preempt code with live values in any register, not just caller-saved. + addi sp, sp, -128 + sw ra, 124(sp) + sw gp, 120(sp) + sw tp, 116(sp) + sw t0, 112(sp) + sw t1, 108(sp) + sw t2, 104(sp) + sw s0, 100(sp) + sw s1, 96(sp) + sw a0, 92(sp) + sw a1, 88(sp) + sw a2, 84(sp) + sw a3, 80(sp) + sw a4, 76(sp) + sw a5, 72(sp) + sw a6, 68(sp) + sw a7, 64(sp) + sw s2, 60(sp) + sw s3, 56(sp) + sw s4, 52(sp) + sw s5, 48(sp) + sw s6, 44(sp) + sw s7, 40(sp) + sw s8, 36(sp) + sw s9, 32(sp) + sw s10, 28(sp) + sw s11, 24(sp) + sw t3, 20(sp) + sw t4, 16(sp) + sw t5, 12(sp) + sw t6, 8(sp) + + csrr t0, mcause + li t1, 0x80000007 # machine timer interrupt (RV32) + bne t0, t1, 1f + call timer_isr # C function that ACKs/clears the timer so i_timer_irq goes low +1: + lw t6, 8(sp) + lw t5, 12(sp) + lw t4, 16(sp) + lw t3, 20(sp) + lw s11, 24(sp) + lw s10, 28(sp) + lw s9, 32(sp) + lw s8, 36(sp) + lw s7, 40(sp) + lw s6, 44(sp) + lw s5, 48(sp) + lw s4, 52(sp) + lw s3, 56(sp) + lw s2, 60(sp) + lw a7, 64(sp) + lw a6, 68(sp) + lw a5, 72(sp) + lw a4, 76(sp) + lw a3, 80(sp) + lw a2, 84(sp) + lw a1, 88(sp) + lw a0, 92(sp) + lw s1, 96(sp) + lw s0, 100(sp) + lw t2, 104(sp) + lw t1, 108(sp) + lw t0, 112(sp) + lw tp, 116(sp) + lw gp, 120(sp) + lw ra, 124(sp) + addi sp, sp, 128 + mret diff --git a/cores/system/test/sw/sweep/sweep.c b/cores/system/test/sw/sweep/sweep.c new file mode 100644 index 0000000..038db42 --- /dev/null +++ b/cores/system/test/sw/sweep/sweep.c @@ -0,0 +1,45 @@ +#include + +#define GPIO_BASE 0x40000000u +static volatile uint32_t * const R_FREQ = (volatile uint32_t *)(GPIO_BASE+0); +static volatile uint32_t * const LEDS = (volatile uint32_t *)(GPIO_BASE+4); +static volatile uint32_t * const LEDGR = (volatile uint32_t *)(GPIO_BASE+8); + +#define TIMER_BASE 0x40010000u +static volatile uint32_t * const TIMER = (volatile uint32_t *)(TIMER_BASE+0); + +#define MSTATUS_MIE (1u << 3) +#define MIE_MTIE (1u << 7) + +extern void trap_entry(); + +static inline void irq_init() { + /* mtvec first */ + asm volatile ("csrw mtvec, %0" :: "r"(trap_entry)); + + /* enable machine timer interrupt */ + asm volatile ("csrs mie, %0" :: "r"(MIE_MTIE)); + + /* global enable last */ + asm volatile ("csrs mstatus, %0" :: "r"(MSTATUS_MIE)); +} + +void timer_isr(){ + static int set = 0; + *TIMER = 1840000*8; + *LEDGR = ~(*LEDGR); +} + +void main(){ + irq_init(); + + *LEDGR = 3; + *TIMER = 1840000*2; + + for(;;){ + for(int i=1000; i<10000; i++){ + *R_FREQ = i; + for(int j=0; j<80; j++) asm volatile("nop"); + } + } +} \ No newline at end of file diff --git a/cores/system/test/test.core b/cores/system/test/test.core index 4697ce0..0702eae 100644 --- a/cores/system/test/test.core +++ b/cores/system/test/test.core @@ -7,12 +7,18 @@ filesets: rtl: depend: - joppeb:primitive:clkgen - - joppeb:wb:jtag_wb_bridge - - joppeb:wb:wb_gpio + - joppeb:system:mcu + - joppeb:signal:nco_q15 + - joppeb:util:conv files: - rtl/toplevel.v file_type: verilogSource + sw: + files: + - sw/sweep/sweep.hex + file_type: user + mimas: files: - mimas.ucf : {file_type : UCF} @@ -28,6 +34,7 @@ targets: filesets: - rtl - mimas + - sw toplevel: toplevel parameters: - FPGA_SPARTAN6=true diff --git a/cores/util/clog2/clog2.vh b/cores/util/clog2/clog2.vh index 0aad108..a273f14 100644 --- a/cores/util/clog2/clog2.vh +++ b/cores/util/clog2/clog2.vh @@ -2,6 +2,7 @@ `define CLOG2_VH // Verilog-2001 compatible ceil(log2(x)) macro (matches $clog2 semantics). +`ifndef CLOG2 `define CLOG2(x) \ (((x) <= 1) ? 0 : \ ((x) <= 2) ? 1 : \ @@ -37,3 +38,4 @@ ((x) <= 2147483648) ? 31 : 32) `endif +`endif diff --git a/cores/util/conv/conv.core b/cores/util/conv/conv.core new file mode 100644 index 0000000..fcbaf68 --- /dev/null +++ b/cores/util/conv/conv.core @@ -0,0 +1,16 @@ +CAPI=2: + +name: joppeb:util:conv:1.0 +description: Verilog conversion helper header + +filesets: + include: + files: + - conv.vh: + is_include_file: true + file_type: verilogSource + +targets: + default: + filesets: + - include diff --git a/cores/util/conv/conv.vh b/cores/util/conv/conv.vh new file mode 100644 index 0000000..b59628b --- /dev/null +++ b/cores/util/conv/conv.vh @@ -0,0 +1,16 @@ +`ifndef CONV_VH +`define CONV_VH + +// ============================================================================= +// Convert Q1.15 to a biased UQ0.16 signal +// ============================================================================= +function [15:0] q15_to_uq16; + input [15:0] q15; + reg [16:0] biased; +begin + biased = q15 + 17'sd32768; + q15_to_uq16 = biased[15:0]; +end +endfunction + +`endif \ No newline at end of file diff --git a/cores/wb/formal_checker/formal/formal_wb_master_checker.v b/cores/wb/formal_checker/formal/formal_wb_master_checker.v index c2addac..485e4c1 100644 --- a/cores/wb/formal_checker/formal/formal_wb_master_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_master_checker.v @@ -52,6 +52,7 @@ module formal_wb_master_checker ( // R3: Once a request starts, hold it stable until the slave responds if( f_past_valid && + !$past(i_rst || i_wb_rst) && $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && !o_wb_ack && !(i_rst || i_wb_rst) diff --git a/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby index 3aa1f4b..175db36 100644 --- a/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby +++ b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby @@ -3,7 +3,7 @@ mode prove depth 8 [engines] -smtbmc z3 +abc pdr [script] {{"-formal"|gen_reads}} diff --git a/cores/wb/jtag_wb_bridge/rtl/jtag_wb_bridge.v b/cores/wb/jtag_wb_bridge/rtl/jtag_wb_bridge.v index bfb9e9e..9cbe2bb 100644 --- a/cores/wb/jtag_wb_bridge/rtl/jtag_wb_bridge.v +++ b/cores/wb/jtag_wb_bridge/rtl/jtag_wb_bridge.v @@ -505,8 +505,8 @@ module jtag_wb_bridge #( // Mark active command complete act_valid <= 1'b0; - // If there is a queued command, promote and start it - if (q_valid) begin + // If there is a queued command and the WB port is idle, promote it now. + if (q_valid && !wb_busy) begin act_valid <= 1'b1; act_opcode <= q_opcode; act_addr <= q_addr; diff --git a/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/Makefile b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/Makefile index 74594cf..7e47956 100644 --- a/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/Makefile +++ b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/Makefile @@ -32,7 +32,7 @@ $(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) + $(CXX) $(LDFLAGS) -o $@ $(APP_OBJS) -L. $(STATIC_LIB) $(LIBS) %.o: %.cpp $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< diff --git a/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp index b657a6b..5a3616e 100644 --- a/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp +++ b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/jtag_wb_bridge_client.cpp @@ -107,7 +107,7 @@ bool JtagWishboneBridge::ping() { ping_value = static_cast(response & 0xffu); if (ping_value != 0xa5) { char msg[96]; - std::snprintf(msg, sizeof(msg), "ping mismatch: expected 0xa4, got 0x%02x", ping_value); + std::snprintf(msg, sizeof(msg), "ping mismatch: expected 0xa5, got 0x%02x", ping_value); return setError(msg); } last_error_.clear(); diff --git a/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/prog.cpp b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/prog.cpp index a25ac0e..24a9184 100644 --- a/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/prog.cpp +++ b/cores/wb/jtag_wb_bridge/tool/libjtag_wb_bridge/prog.cpp @@ -31,15 +31,10 @@ int main(int argc, char** argv) { return -1; } - uint8_t ping_value = 0; if (!bridge.ping()) { 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"); diff --git a/cores/wb/jtag_wb_bridge/tool/test.py b/cores/wb/jtag_wb_bridge/tool/test.py index 1f298f2..30d9a9d 100644 --- a/cores/wb/jtag_wb_bridge/tool/test.py +++ b/cores/wb/jtag_wb_bridge/tool/test.py @@ -5,4 +5,4 @@ with JtagBridge() as bridge: bridge.clear_flags() bridge.ping() - bridge.write32(0x0, 0xAA) \ No newline at end of file + bridge.write8(0x0, 0xAA) \ No newline at end of file diff --git a/cores/wb/wb_arbiter/rtl/arbiter.v b/cores/wb/wb_arbiter/rtl/arbiter.v new file mode 100644 index 0000000..07ada9a --- /dev/null +++ b/cores/wb/wb_arbiter/rtl/arbiter.v @@ -0,0 +1,138 @@ +/** + * Module: arbiter + * + * Description: + * A look ahead, round-robing parameterized arbiter. + * + * <> request + * each bit is controlled by an actor and each actor can 'request' ownership + * of the shared resource by bring high its request bit. + * + * <> grant + * when an actor has been given ownership of shared resource its 'grant' bit + * is driven high + * + * <> select + * binary representation of the grant signal (optional use) + * + * <> active + * is brought high by the arbiter when (any) actor has been given ownership + * of shared resource. + * + * + * Created: Sat Jun 1 20:26:44 EDT 2013 + * + * Author: Berin Martini // berin.martini@gmail.com + */ + `ifndef _arbiter_ `define _arbiter_ + `include "clog2.vh" + +module arbiter + #(parameter + NUM_PORTS = 6, + SEL_WIDTH = ((NUM_PORTS > 1) ? `CLOG2(NUM_PORTS) : 1)) + (input wire clk, + input wire rst, + input wire [NUM_PORTS-1:0] request, + output reg [NUM_PORTS-1:0] grant, + output reg [SEL_WIDTH-1:0] select, + output reg active +); + + /** + * Local parameters + */ + + localparam WRAP_LENGTH = 2*NUM_PORTS; + + + // Find First 1 - Start from MSB and count downwards, returns 0 when no + // bit set + function [SEL_WIDTH-1:0] ff1 ( + input [NUM_PORTS-1:0] in + ); + reg set; + integer i; + + begin + set = 1'b0; + ff1 = 'b0; + + for (i = 0; i < NUM_PORTS; i = i + 1) begin + if (in[i] & ~set) begin + set = 1'b1; + ff1 = i[0 +: SEL_WIDTH]; + end + end + end + endfunction + + +`ifdef VERBOSE + initial $display("Bus arbiter with %d units", NUM_PORTS); +`endif + + + /** + * Internal signals + */ + + integer yy; + + wire next; + wire [NUM_PORTS-1:0] order; + + reg [NUM_PORTS-1:0] token; + wire [NUM_PORTS-1:0] token_lookahead [NUM_PORTS-1:0]; + wire [WRAP_LENGTH-1:0] token_wrap; + + + /** + * Implementation + */ + + assign token_wrap = {token, token}; + + assign next = ~|(token & request); + + + always @(posedge clk) + grant <= token & request; + + + always @(posedge clk) + select <= ff1(token & request); + + + always @(posedge clk) + active <= |(token & request); + + + always @(posedge clk) + if (rst) token <= 'b1; + else if (next) begin + + for (yy = 0; yy < NUM_PORTS; yy = yy + 1) begin : TOKEN_ + + if (order[yy]) begin + token <= token_lookahead[yy]; + end + end + end + + + genvar xx; + generate + for (xx = 0; xx < NUM_PORTS; xx = xx + 1) begin : ORDER_ + + assign token_lookahead[xx] = token_wrap[xx +: NUM_PORTS]; + + assign order[xx] = |(token_lookahead[xx] & request); + + end + endgenerate + + +endmodule + +`endif // `ifndef _arbiter_ diff --git a/cores/wb/wb_arbiter/rtl/wb_arbiter.v b/cores/wb/wb_arbiter/rtl/wb_arbiter.v new file mode 100644 index 0000000..899a641 --- /dev/null +++ b/cores/wb/wb_arbiter/rtl/wb_arbiter.v @@ -0,0 +1,101 @@ +/* wb_arbiter. Part of wb_intercon + * + * ISC License + * + * Copyright (C) 2013-2019 Olof Kindgren + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + Wishbone arbiter, burst-compatible + Simple round-robin arbiter for multiple Wishbone masters + */ +`include "clog2.vh" + +module wb_arbiter + #(parameter dw = 32, + parameter aw = 32, + parameter num_hosts = 0, + parameter num_masters = num_hosts) + ( + input wire wb_clk_i, + input wire wb_rst_i, + + // Wishbone Master Interface + input wire [num_masters*aw-1:0] wbm_adr_i, + input wire [num_masters*dw-1:0] wbm_dat_i, + input wire [num_masters*4-1:0] wbm_sel_i, + input wire [num_masters-1:0] wbm_we_i, + input wire [num_masters-1:0] wbm_cyc_i, + input wire [num_masters-1:0] wbm_stb_i, + input wire [num_masters*3-1:0] wbm_cti_i, + input wire [num_masters*2-1:0] wbm_bte_i, + output wire [num_masters*dw-1:0] wbm_dat_o, + output wire [num_masters-1:0] wbm_ack_o, + output wire [num_masters-1:0] wbm_err_o, + output wire [num_masters-1:0] wbm_rty_o, + + // Wishbone Slave interface + output wire [aw-1:0] wbs_adr_o, + output wire [dw-1:0] wbs_dat_o, + output wire [3:0] wbs_sel_o, + output wire wbs_we_o, + output wire wbs_cyc_o, + output wire wbs_stb_o, + output wire [2:0] wbs_cti_o, + output wire [1:0] wbs_bte_o, + input wire [dw-1:0] wbs_dat_i, + input wire wbs_ack_i, + input wire wbs_err_i, + input wire wbs_rty_i); + + +/////////////////////////////////////////////////////////////////////////////// +// Parameters +/////////////////////////////////////////////////////////////////////////////// + + //Use parameter instead of localparam to work around a bug in Xilinx ISE + parameter master_sel_bits = num_masters > 1 ? `CLOG2(num_masters) : 1; + + wire [num_masters-1:0] grant; + wire [master_sel_bits-1:0] master_sel; + wire active; + + arbiter + #(.NUM_PORTS (num_masters)) + arbiter0 + (.clk (wb_clk_i), + .rst (wb_rst_i), + .request (wbm_cyc_i), + .grant (grant), + .select (master_sel), + .active (active)); +/* verilator lint_off WIDTH */ + //Mux active master + assign wbs_adr_o = wbm_adr_i[master_sel*aw+:aw]; + assign wbs_dat_o = wbm_dat_i[master_sel*dw+:dw]; + assign wbs_sel_o = wbm_sel_i[master_sel*4+:4]; + assign wbs_we_o = wbm_we_i [master_sel]; + assign wbs_cyc_o = wbm_cyc_i[master_sel] & active; + assign wbs_stb_o = wbm_stb_i[master_sel]; + assign wbs_cti_o = wbm_cti_i[master_sel*3+:3]; + assign wbs_bte_o = wbm_bte_i[master_sel*2+:2]; + + assign wbm_dat_o = {num_masters{wbs_dat_i}}; + assign wbm_ack_o = ((wbs_ack_i & active) << master_sel); + assign wbm_err_o = ((wbs_err_i & active) << master_sel); + assign wbm_rty_o = ((wbs_rty_i & active) << master_sel); +/* verilator lint_on WIDTH */ + +endmodule // wb_arbiter diff --git a/cores/wb/wb_arbiter/wb_arbiter.core b/cores/wb/wb_arbiter/wb_arbiter.core new file mode 100644 index 0000000..df864f8 --- /dev/null +++ b/cores/wb/wb_arbiter/wb_arbiter.core @@ -0,0 +1,42 @@ +CAPI=2: + +name: joppeb:wb:wb_arbiter:1.0 +description: Wishbone round-robin arbiter + +filesets: + rtl: + depend: + - joppeb:util:clog2 + files: + - rtl/arbiter.v + - rtl/wb_arbiter.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: wb_arbiter + parameters: + - dw + - aw + - num_hosts + - num_masters + +parameters: + dw: + datatype: int + description: Wishbone data width + paramtype: vlogparam + aw: + datatype: int + description: Wishbone address width + paramtype: vlogparam + num_hosts: + datatype: int + description: Deprecated alias for num_masters + paramtype: vlogparam + num_masters: + datatype: int + description: Number of wishbone masters + paramtype: vlogparam diff --git a/cores/wb/wb_gpio/wb_gpio.core b/cores/wb/wb_gpio/wb_gpio.core index c1a4ec1..32e62e0 100644 --- a/cores/wb/wb_gpio/wb_gpio.core +++ b/cores/wb/wb_gpio/wb_gpio.core @@ -8,6 +8,7 @@ filesets: files: - rtl/wb_gpio.v file_type: verilogSource + formal_rtl: depend: - joppeb:wb:formal_checker @@ -26,6 +27,7 @@ targets: toplevel: wb_gpio parameters: - address + formal: default_tool: symbiyosys filesets: diff --git a/cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v b/cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v new file mode 100644 index 0000000..48f4936 --- /dev/null +++ b/cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v @@ -0,0 +1,63 @@ +`default_nettype none + +module wb_gpio_banks #( + parameter integer NUM_BANKS = 4, + parameter [31:0] BASE_ADDR = 32'h8000_0000 +) ( + input wire i_wb_clk, + 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 [NUM_BANKS*32-1:0] i_gpio, + output reg [31:0] o_wb_rdt, + output reg o_wb_ack, + output wire [NUM_BANKS*32-1:0] o_gpio +); + + wire [NUM_BANKS-1:0] bank_sel; + wire [NUM_BANKS-1:0] bank_stb; + wire [NUM_BANKS*32-1:0] bank_rdt; + wire [NUM_BANKS-1:0] bank_ack; + + genvar gi; + generate + for (gi = 0; gi < NUM_BANKS; gi = gi + 1) begin : gen_gpio + localparam [31:0] BANK_ADDR = BASE_ADDR + (gi * 4); + + assign bank_sel[gi] = (i_wb_adr == BANK_ADDR); + assign bank_stb[gi] = i_wb_stb & bank_sel[gi]; + + wb_gpio #( + .address(BANK_ADDR) + ) u_gpio ( + .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(bank_stb[gi]), + .i_gpio(i_gpio[gi*32 +: 32]), + .o_wb_rdt(bank_rdt[gi*32 +: 32]), + .o_wb_ack(bank_ack[gi]), + .o_gpio(o_gpio[gi*32 +: 32]) + ); + end + endgenerate + + integer bi; + always @* begin + o_wb_rdt = 32'h0000_0000; + o_wb_ack = 1'b0; + for (bi = 0; bi < NUM_BANKS; bi = bi + 1) begin + if (bank_sel[bi]) begin + o_wb_rdt = bank_rdt[bi*32 +: 32]; + o_wb_ack = bank_ack[bi]; + end + end + end + +endmodule diff --git a/cores/wb/wb_gpio_banks/wb_gpio_banks.core b/cores/wb/wb_gpio_banks/wb_gpio_banks.core new file mode 100644 index 0000000..c91c852 --- /dev/null +++ b/cores/wb/wb_gpio_banks/wb_gpio_banks.core @@ -0,0 +1,31 @@ +CAPI=2: + +name: joppeb:wb:wb_gpio_banks:1.0 +description: Wishbone GPIO bank wrapper + +filesets: + rtl: + depend: + - joppeb:wb:wb_gpio + files: + - rtl/wb_gpio_banks.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: wb_gpio_banks + parameters: + - NUM_BANKS + - BASE_ADDR + +parameters: + NUM_BANKS: + datatype: int + description: Number of GPIO banks to instantiate + paramtype: vlogparam + BASE_ADDR: + datatype: int + description: Base wishbone address for bank 0 + paramtype: vlogparam diff --git a/cores/wb/wb_mux/rtl/wb_mux.v b/cores/wb/wb_mux/rtl/wb_mux.v new file mode 100644 index 0000000..d93ae99 --- /dev/null +++ b/cores/wb/wb_mux/rtl/wb_mux.v @@ -0,0 +1,145 @@ +/* wb_mux. Part of wb_intercon + * + * ISC License + * + * Copyright (C) 2013-2019 Olof Kindgren + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + Wishbone multiplexer, burst-compatible + + Simple mux with an arbitrary number of slaves. + + The parameters MATCH_ADDR and MATCH_MASK are flattened arrays + aw*NUM_SLAVES sized arrays that are used to calculate the + active slave. slave i is selected when + (wb_adr_i & MATCH_MASK[(i+1)*aw-1:i*aw] is equal to + MATCH_ADDR[(i+1)*aw-1:i*aw] + If several regions are overlapping, the slave with the lowest + index is selected. This can be used to have fallback + functionality in the last slave, in case no other slave was + selected. + + If no match is found, the wishbone transaction will stall and + an external watchdog is required to abort the transaction + + Todo: + Registered master/slave connections + Rewrite with System Verilog 2D arrays when tools support them +*/ +`include "clog2.vh" + +module wb_mux + #(parameter dw = 32, // Data width + parameter aw = 32, // Address width + parameter num_devices = 2, // Number of devices + parameter num_slaves = num_devices, // Number of devices (deprecated) + parameter [num_slaves*aw-1:0] MATCH_ADDR = 0, + parameter [num_slaves*aw-1:0] MATCH_MASK = 0) + + ( + input wire wb_clk_i, + input wire wb_rst_i, + + // Master Interface + input wire [aw-1:0] wbm_adr_i, + input wire [dw-1:0] wbm_dat_i, + input wire [3:0] wbm_sel_i, + input wire wbm_we_i, + input wire wbm_cyc_i, + input wire wbm_stb_i, + input wire [2:0] wbm_cti_i, + input wire [1:0] wbm_bte_i, + output wire [dw-1:0] wbm_dat_o, + output wire wbm_ack_o, + output wire wbm_err_o, + output wire wbm_rty_o, + // Wishbone Slave interface + output wire [num_slaves*aw-1:0] wbs_adr_o, + output wire [num_slaves*dw-1:0] wbs_dat_o, + output wire [num_slaves*4-1:0] wbs_sel_o, + output wire [num_slaves-1:0] wbs_we_o, + output wire [num_slaves-1:0] wbs_cyc_o, + output wire [num_slaves-1:0] wbs_stb_o, + output wire [num_slaves*3-1:0] wbs_cti_o, + output wire [num_slaves*2-1:0] wbs_bte_o, + input wire [num_slaves*dw-1:0] wbs_dat_i, + input wire [num_slaves-1:0] wbs_ack_i, + input wire [num_slaves-1:0] wbs_err_i, + input wire [num_slaves-1:0] wbs_rty_i); + +/////////////////////////////////////////////////////////////////////////////// +// Master/slave connection +/////////////////////////////////////////////////////////////////////////////// + + //Use parameter instead of localparam to work around a bug in Xilinx ISE + parameter slave_sel_bits = num_slaves > 1 ? `CLOG2(num_slaves) : 1; + + reg wbm_err; + wire [slave_sel_bits-1:0] slave_sel; + wire [num_slaves-1:0] match; + + genvar idx; + + generate + for(idx=0; idx= 0; i=i-1) begin + if (in[i]) +/* verilator lint_off WIDTH */ + ff1 = i; +/* verilator lint_on WIDTH */ + end + end + endfunction + + assign slave_sel = ff1(match); + + always @(posedge wb_clk_i) + wbm_err <= wbm_cyc_i & !(|match); + + assign wbs_adr_o = {num_slaves{wbm_adr_i}}; + assign wbs_dat_o = {num_slaves{wbm_dat_i}}; + assign wbs_sel_o = {num_slaves{wbm_sel_i}}; + assign wbs_we_o = {num_slaves{wbm_we_i}}; +/* verilator lint_off WIDTH */ + + // Expand master CYC to slave bus width before shifting to one-hot select. + // Shifting a 1-bit signal would otherwise zero out all but slave 0. + assign wbs_cyc_o = match & ({num_slaves{wbm_cyc_i}} << slave_sel); +/* verilator lint_on WIDTH */ + assign wbs_stb_o = {num_slaves{wbm_stb_i}}; + + assign wbs_cti_o = {num_slaves{wbm_cti_i}}; + assign wbs_bte_o = {num_slaves{wbm_bte_i}}; + + assign wbm_dat_o = wbs_dat_i[slave_sel*dw+:dw]; + assign wbm_ack_o = wbs_ack_i[slave_sel]; + assign wbm_err_o = wbs_err_i[slave_sel] | wbm_err; + assign wbm_rty_o = wbs_rty_i[slave_sel]; + +endmodule diff --git a/cores/wb/wb_mux/wb_mux.core b/cores/wb/wb_mux/wb_mux.core new file mode 100644 index 0000000..6f56632 --- /dev/null +++ b/cores/wb/wb_mux/wb_mux.core @@ -0,0 +1,51 @@ +CAPI=2: + +name: joppeb:wb:wb_mux:1.0 +description: Wishbone address decoder and multiplexer + +filesets: + rtl: + depend: + - joppeb:util:clog2 + files: + - rtl/wb_mux.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: wb_mux + parameters: + - dw + - aw + - num_devices + - num_slaves + - MATCH_ADDR + - MATCH_MASK + +parameters: + dw: + datatype: int + description: Wishbone data width + paramtype: vlogparam + aw: + datatype: int + description: Wishbone address width + paramtype: vlogparam + num_devices: + datatype: int + description: Deprecated alias for num_slaves + paramtype: vlogparam + num_slaves: + datatype: int + description: Number of wishbone slaves + paramtype: vlogparam + MATCH_ADDR: + datatype: int + description: Flattened slave address match table + paramtype: vlogparam + MATCH_MASK: + datatype: int + description: Flattened slave address mask table + paramtype: vlogparam diff --git a/cores/wb/wb_timer/rtl/wb_timer.v b/cores/wb/wb_timer/rtl/wb_timer.v new file mode 100644 index 0000000..ac14586 --- /dev/null +++ b/cores/wb/wb_timer/rtl/wb_timer.v @@ -0,0 +1,74 @@ +`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 +)( + input wire i_clk, + input wire i_rst, + output reg o_irq, + + input wire [31:0] i_wb_dat, + output reg [31:0] o_wb_dat, + 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; + + // 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); + + // Readback: expose the current counter value + always @(*) begin + o_wb_dat = 32'd0; + o_wb_dat[WIDTH-1:0] = counter; + end + + // Main logic + always @(posedge i_clk) begin + if (i_rst) begin + counter <= {WIDTH{1'b0}}; + presc <= { (DIVIDER+1){1'b0} }; + o_irq <= 1'b0; + 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; + + // reset prescaler on (re)start or stop + presc <= { (DIVIDER+1){1'b0} }; + + 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 + end + end + end + end + +endmodule \ No newline at end of file diff --git a/cores/wb/wb_timer/wb_timer.core b/cores/wb/wb_timer/wb_timer.core new file mode 100644 index 0000000..628aff2 --- /dev/null +++ b/cores/wb/wb_timer/wb_timer.core @@ -0,0 +1,29 @@ +CAPI=2: + +name: joppeb:wb:wb_timer:1.0 +description: Wishbone countdown timer peripheral + +filesets: + rtl: + files: + - rtl/wb_timer.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: wb_countdown_timer + parameters: + - WIDTH + - DIVIDER + +parameters: + WIDTH: + datatype: int + description: Counter width in bits + paramtype: vlogparam + DIVIDER: + datatype: int + description: Prescaler divider as a power of two exponent + paramtype: vlogparam diff --git a/fusesoc.conf b/fusesoc.conf index 3eb1efc..ac3f322 100644 --- a/fusesoc.conf +++ b/fusesoc.conf @@ -4,3 +4,14 @@ sync-uri = ./cores sync-type = local auto-sync = true +[library.serv] +location = ./fusesoc_libraries/serv +sync-uri = git@github.com:Jojojoppe/serv.git +sync-type = local +auto-sync = true + +[library.fusesoc-cores] +location = ./fusesoc_libraries/fusesoc-cores +sync-uri = https://github.com/fusesoc/fusesoc-cores +sync-type = git +auto-sync = true diff --git a/fusesoc_libraries/fusesoc-cores b/fusesoc_libraries/fusesoc-cores new file mode 160000 index 0000000..815f64b --- /dev/null +++ b/fusesoc_libraries/fusesoc-cores @@ -0,0 +1 @@ +Subproject commit 815f64ba335db54a7904eed862c970dc66ac7018 diff --git a/fusesoc_libraries/serv b/fusesoc_libraries/serv new file mode 160000 index 0000000..4999793 --- /dev/null +++ b/fusesoc_libraries/serv @@ -0,0 +1 @@ +Subproject commit 4999793a48485e2104155048859dc8506b719a50 From 8289b0d090baa1f8cebd4f0e82eda691adbe78e9 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sun, 1 Mar 2026 13:52:41 +0100 Subject: [PATCH 05/12] Added wb formal script and added other sby tasks --- cores/wb/check_formal.sh | 89 +++++++++++++++++++ .../formal/formal_wb_master_checker.v | 27 ++++++ .../formal/formal_wb_slave_checker.v | 24 +++++ .../jtag_wb_bridge/formal/jtag_wb_bridge.sby | 16 +++- cores/wb/wb_gpio/formal/wb_gpio.sby | 18 +++- cores/wb/wb_mem32/formal/wb_mem32.sby | 18 +++- 6 files changed, 181 insertions(+), 11 deletions(-) create mode 100755 cores/wb/check_formal.sh diff --git a/cores/wb/check_formal.sh b/cores/wb/check_formal.sh new file mode 100755 index 0000000..629685e --- /dev/null +++ b/cores/wb/check_formal.sh @@ -0,0 +1,89 @@ +#!/bin/sh + +# Add or remove cores here. Each entry should be a full FuseSoC VLNV. +CORES=" +joppeb:wb:wb_mem32 +joppeb:wb:wb_gpio +joppeb:wb:jtag_wb_bridge +" + +# Add or remove formal tasks here. +TASKS=" +bmc +cover +prove +" + +total=0 +passed=0 +failed=0 +passed_runs="" +failed_runs="" + +run_task() { + core="$1" + task="$2" + label="$core [$task]" + log_file="" + total=$((total + 1)) + + printf '\n[%d] Running formal %s for %s\n' "$total" "$task" "$core" + + log_file=$(mktemp /tmp/check_formal.XXXXXX) || exit 2 + + if \ + FUSESOC_CORE="$core" \ + FUSESOC_TASK="$task" \ + script -qefc 'fusesoc run --target formal "$FUSESOC_CORE" --taskname "$FUSESOC_TASK"' "$log_file" \ + >/dev/null 2>&1 + then + passed=$((passed + 1)) + passed_runs="$passed_runs +$label" + printf 'Result: PASS (%s)\n' "$label" + rm -f "$log_file" + return 0 + else + failed=$((failed + 1)) + failed_runs="$failed_runs +$label" + printf 'Result: FAIL (%s)\n' "$label" + printf 'Captured log for %s:\n' "$label" + cat "$log_file" | grep summary + rm -f "$log_file" + return 1 + fi +} + +for core in $CORES; do + for task in $TASKS; do + run_task "$core" "$task" + done +done + +printf '\nFormal run summary\n' +printf ' Total: %d\n' "$total" +printf ' Passed: %d\n' "$passed" +printf ' Failed: %d\n' "$failed" + +if [ -n "$passed_runs" ]; then + printf '\nPassed runs:\n' + printf '%s\n' "$passed_runs" | while IFS= read -r run; do + if [ -n "$run" ]; then + printf ' - %s\n' "$run" + fi + done +fi + +if [ -n "$failed_runs" ]; then + printf '\nFailed runs:\n' + printf '%s\n' "$failed_runs" | while IFS= read -r run; do + if [ -n "$run" ]; then + printf ' - %s\n' "$run" + fi + done +fi + +if [ "$failed" -ne 0 ]; then + exit 1 +fi diff --git a/cores/wb/formal_checker/formal/formal_wb_master_checker.v b/cores/wb/formal_checker/formal/formal_wb_master_checker.v index 485e4c1..e663660 100644 --- a/cores/wb/formal_checker/formal/formal_wb_master_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_master_checker.v @@ -68,6 +68,33 @@ module formal_wb_master_checker ( // R4: Once CYC is low, STB must also be low if(!i_wb_cyc) assert(!i_wb_stb); + + // C0: We eventually initiate a request + cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb); + + // C1: We eventually get an ACK during an active request + cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); + + // C2: ACK in the *same* cycle as the request (allowed by your A1) + cover(f_past_valid && !i_rst && !i_wb_rst && (i_wb_cyc && i_wb_stb) && o_wb_ack); + + // C3: ACK one cycle *after* the request (also allowed by your A1) + // cover(f_past_valid && !i_rst && !i_wb_rst && + // $past(i_wb_cyc && i_wb_stb) && o_wb_ack && !(i_wb_cyc && i_wb_stb)); + + // C4: A “wait state” happens: request asserted, no ACK for at least 1 cycle + cover(f_past_valid && !i_rst && !i_wb_rst && + $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && + (i_wb_cyc && i_wb_stb && !o_wb_ack)); + + // C5: Read and write both occur (even if only once each) + cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && !i_wb_we); + cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && i_wb_we); + + // C6: A transfer completes and the master drops CYC sometime after + cover(f_past_valid && !i_rst && !i_wb_rst && + $past(i_wb_cyc && i_wb_stb && o_wb_ack) && !i_wb_cyc); + end wire unused = &{1'b0, o_wb_rdt}; diff --git a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v index 68db9d7..1295f6e 100644 --- a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v @@ -62,6 +62,30 @@ module formal_wb_slave_checker ( !i_wb_stb ) assert(!o_wb_ack); + + // C0: A request occurs at all + cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb); + + // C1: A request with write and with read occur + cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && i_wb_we); + cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && !i_wb_we); + + // C2: ACK happens during a request (basic progress) + cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); + + // C3: ACK same-cycle vs one-cycle-late (mirrors your R1 definition) + cover(f_past_valid && !i_rst && !i_wb_rst && (i_wb_cyc && i_wb_stb) && o_wb_ack); + cover(f_past_valid && !i_rst && !i_wb_rst && $past(i_wb_cyc && i_wb_stb) && o_wb_ack); + + // C4: “wait state” from the slave POV: request persists without ACK + cover(f_past_valid && !i_rst && !i_wb_rst && + $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && + (i_wb_cyc && i_wb_stb && !o_wb_ack)); + + // C5: Master ends a cycle (CYC drops) after at least one request + cover(f_past_valid && !i_rst && !i_wb_rst && + $past(i_wb_cyc && i_wb_stb) && !i_wb_cyc); + end wire unused = &{1'b0, o_wb_rdt}; diff --git a/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby index 175db36..75c2d00 100644 --- a/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby +++ b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge.sby @@ -1,9 +1,19 @@ +[tasks] +prove +cover +bmc + [options] -mode prove -depth 8 +bmc: mode bmc +bmc: depth 16 +cover: mode cover +cover: depth 16 +prove: mode prove [engines] -abc pdr +bmc: smtbmc yices +cover: smtbmc yices +prove: abc pdr [script] {{"-formal"|gen_reads}} diff --git a/cores/wb/wb_gpio/formal/wb_gpio.sby b/cores/wb/wb_gpio/formal/wb_gpio.sby index 0fbf6d2..a5088be 100644 --- a/cores/wb/wb_gpio/formal/wb_gpio.sby +++ b/cores/wb/wb_gpio/formal/wb_gpio.sby @@ -1,13 +1,23 @@ +[tasks] +prove +cover +bmc + [options] -mode prove -depth 8 +bmc: mode bmc +bmc: depth 50 +cover: mode cover +cover: depth 50 +prove: mode prove [engines] -smtbmc z3 parallel.enable=true parallel.threads.max=8 +bmc: smtbmc yices +cover: smtbmc yices +prove: abc pdr [script] {{"-formal"|gen_reads}} prep -top {{top_level}} [files] -{{files}} +{{files}} \ No newline at end of file diff --git a/cores/wb/wb_mem32/formal/wb_mem32.sby b/cores/wb/wb_mem32/formal/wb_mem32.sby index 2663fc0..670aaf9 100644 --- a/cores/wb/wb_mem32/formal/wb_mem32.sby +++ b/cores/wb/wb_mem32/formal/wb_mem32.sby @@ -1,9 +1,19 @@ +[tasks] +prove +cover +bmc + [options] -mode prove -depth 8 +bmc: mode bmc +bmc: depth 50 +cover: mode cover +cover: depth 50 +prove: mode prove [engines] -smtbmc z3 parallel.enable=true parallel.threads.max=8 +bmc: smtbmc yices +cover: smtbmc yices +prove: abc pdr [script] read -formal clog2.vh @@ -12,4 +22,4 @@ prep -top {{top_level}} [files] src/joppeb_util_clog2_1.0/clog2.vh -{{files}} +{{files}} \ No newline at end of file From 7b46ae5e87b66a6516eae94ffc487d7b45a3e7c6 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sun, 1 Mar 2026 14:12:12 +0100 Subject: [PATCH 06/12] Some cleanup and added formal for the banks and timer --- cores/wb/check_formal.sh | 2 + .../formal/formal_wb_master_checker.v | 10 ++- .../formal/formal_wb_slave_checker.v | 23 ++++--- .../formal/formal_wb_gpio_banks.v | 53 +++++++++++++++ .../wb/wb_gpio_banks/formal/wb_gpio_banks.sby | 23 +++++++ cores/wb/wb_gpio_banks/wb_gpio_banks.core | 20 ++++++ cores/wb/wb_timer/formal/formal_wb_timer.v | 66 +++++++++++++++++++ cores/wb/wb_timer/formal/wb_timer.sby | 23 +++++++ cores/wb/wb_timer/wb_timer.core | 20 ++++++ 9 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 cores/wb/wb_gpio_banks/formal/formal_wb_gpio_banks.v create mode 100644 cores/wb/wb_gpio_banks/formal/wb_gpio_banks.sby create mode 100644 cores/wb/wb_timer/formal/formal_wb_timer.v create mode 100644 cores/wb/wb_timer/formal/wb_timer.sby diff --git a/cores/wb/check_formal.sh b/cores/wb/check_formal.sh index 629685e..4df22a7 100755 --- a/cores/wb/check_formal.sh +++ b/cores/wb/check_formal.sh @@ -4,7 +4,9 @@ CORES=" joppeb:wb:wb_mem32 joppeb:wb:wb_gpio +joppeb:wb:wb_gpio_banks joppeb:wb:jtag_wb_bridge +joppeb:wb:wb_timer " # Add or remove formal tasks here. diff --git a/cores/wb/formal_checker/formal/formal_wb_master_checker.v b/cores/wb/formal_checker/formal/formal_wb_master_checker.v index e663660..68dc7c6 100644 --- a/cores/wb/formal_checker/formal/formal_wb_master_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_master_checker.v @@ -75,12 +75,10 @@ module formal_wb_master_checker ( // C1: We eventually get an ACK during an active request cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); - // C2: ACK in the *same* cycle as the request (allowed by your A1) - cover(f_past_valid && !i_rst && !i_wb_rst && (i_wb_cyc && i_wb_stb) && o_wb_ack); - - // C3: ACK one cycle *after* the request (also allowed by your A1) - // cover(f_past_valid && !i_rst && !i_wb_rst && - // $past(i_wb_cyc && i_wb_stb) && o_wb_ack && !(i_wb_cyc && i_wb_stb)); + // C2: A delayed ACK occurs for a request issued in a previous cycle. + // This does not require the request to drop in the ACK cycle. + cover(f_past_valid && !i_rst && !i_wb_rst && + $past(i_wb_cyc && i_wb_stb) && o_wb_ack); // C4: A “wait state” happens: request asserted, no ACK for at least 1 cycle cover(f_past_valid && !i_rst && !i_wb_rst && diff --git a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v index 1295f6e..866e730 100644 --- a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v @@ -1,6 +1,8 @@ `timescale 1ns/1ps -module formal_wb_slave_checker ( +module formal_wb_slave_checker #( + parameter combinatorial_ack = 0 +) ( input wire i_clk, input wire i_rst, input wire i_wb_rst, @@ -73,14 +75,19 @@ module formal_wb_slave_checker ( // C2: ACK happens during a request (basic progress) cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); - // C3: ACK same-cycle vs one-cycle-late (mirrors your R1 definition) - cover(f_past_valid && !i_rst && !i_wb_rst && (i_wb_cyc && i_wb_stb) && o_wb_ack); - cover(f_past_valid && !i_rst && !i_wb_rst && $past(i_wb_cyc && i_wb_stb) && o_wb_ack); + // C3: Exercise the expected ACK timing style for this slave. + if (combinatorial_ack) begin + cover(f_past_valid && !i_rst && !i_wb_rst && + (i_wb_cyc && i_wb_stb) && o_wb_ack); + end else begin + cover(f_past_valid && !i_rst && !i_wb_rst && + $past(i_wb_cyc && i_wb_stb) && o_wb_ack); - // C4: “wait state” from the slave POV: request persists without ACK - cover(f_past_valid && !i_rst && !i_wb_rst && - $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && - (i_wb_cyc && i_wb_stb && !o_wb_ack)); + // C4: Wait-state behavior for registered/non-zero-wait slaves. + cover(f_past_valid && !i_rst && !i_wb_rst && + $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && + (i_wb_cyc && i_wb_stb && !o_wb_ack)); + end // C5: Master ends a cycle (CYC drops) after at least one request cover(f_past_valid && !i_rst && !i_wb_rst && diff --git a/cores/wb/wb_gpio_banks/formal/formal_wb_gpio_banks.v b/cores/wb/wb_gpio_banks/formal/formal_wb_gpio_banks.v new file mode 100644 index 0000000..505d4c1 --- /dev/null +++ b/cores/wb/wb_gpio_banks/formal/formal_wb_gpio_banks.v @@ -0,0 +1,53 @@ +`timescale 1ns/1ps + +module formal_wb_gpio_banks #( + parameter integer NUM_BANKS = 2, + parameter [31:0] BASE_ADDR = 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 [NUM_BANKS*32-1:0] i_gpio; + wire [31:0] o_wb_rdt; + wire o_wb_ack; + wire [NUM_BANKS*32-1:0] o_gpio; + wire i_wb_cyc; + + assign i_wb_cyc = i_wb_stb || o_wb_ack; + + wb_gpio_banks #( + .NUM_BANKS(NUM_BANKS), + .BASE_ADDR(BASE_ADDR) + ) 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) + ); +endmodule diff --git a/cores/wb/wb_gpio_banks/formal/wb_gpio_banks.sby b/cores/wb/wb_gpio_banks/formal/wb_gpio_banks.sby new file mode 100644 index 0000000..be55a8b --- /dev/null +++ b/cores/wb/wb_gpio_banks/formal/wb_gpio_banks.sby @@ -0,0 +1,23 @@ +[tasks] +prove +cover +bmc + +[options] +bmc: mode bmc +bmc: depth 50 +cover: mode cover +cover: depth 50 +prove: mode prove + +[engines] +bmc: smtbmc yices +cover: smtbmc yices +prove: abc pdr + +[script] +{{"-formal"|gen_reads}} +prep -top {{top_level}} + +[files] +{{files}} diff --git a/cores/wb/wb_gpio_banks/wb_gpio_banks.core b/cores/wb/wb_gpio_banks/wb_gpio_banks.core index c91c852..0fac524 100644 --- a/cores/wb/wb_gpio_banks/wb_gpio_banks.core +++ b/cores/wb/wb_gpio_banks/wb_gpio_banks.core @@ -10,6 +10,16 @@ filesets: files: - rtl/wb_gpio_banks.v file_type: verilogSource + formal_rtl: + depend: + - joppeb:wb:formal_checker + files: + - formal/formal_wb_gpio_banks.v + file_type: verilogSource + formal_cfg: + files: + - formal/wb_gpio_banks.sby + file_type: sbyConfigTemplate targets: default: @@ -19,6 +29,16 @@ targets: parameters: - NUM_BANKS - BASE_ADDR + formal: + default_tool: symbiyosys + filesets: + - rtl + - formal_rtl + - formal_cfg + toplevel: formal_wb_gpio_banks + parameters: + - NUM_BANKS + - BASE_ADDR parameters: NUM_BANKS: diff --git a/cores/wb/wb_timer/formal/formal_wb_timer.v b/cores/wb/wb_timer/formal/formal_wb_timer.v new file mode 100644 index 0000000..848c8e3 --- /dev/null +++ b/cores/wb/wb_timer/formal/formal_wb_timer.v @@ -0,0 +1,66 @@ +`timescale 1ns/1ps + +module formal_wb_timer #( + parameter WIDTH = 8, + parameter DIVIDER = 0 +); + (* gclk *) reg i_clk; + (* anyseq *) reg i_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_cyc; + (* anyseq *) reg i_wb_stb; + wire [31:0] o_wb_dat; + wire o_wb_ack; + wire o_irq; + wire i_wb_rst; + reg f_past_valid; + + assign i_wb_rst = 1'b0; + + wb_countdown_timer #( + .WIDTH(WIDTH), + .DIVIDER(DIVIDER) + ) dut ( + .i_clk(i_clk), + .i_rst(i_rst), + .o_irq(o_irq), + .i_wb_dat(i_wb_dat), + .o_wb_dat(o_wb_dat), + .i_wb_we(i_wb_we), + .i_wb_cyc(i_wb_cyc), + .i_wb_stb(i_wb_stb), + .o_wb_ack(o_wb_ack) + ); + + formal_wb_slave_checker #( + .combinatorial_ack(1) + ) 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_dat), + .o_wb_ack(o_wb_ack) + ); + + initial f_past_valid = 1'b0; + + always @(posedge i_clk) begin + f_past_valid <= 1'b1; + + // Keep the bus idle on the first cycle after reset so the zero-wait ACK + // does not collide with the generic slave checker's post-reset rule. + if (f_past_valid && $past(i_rst)) begin + assume(!i_wb_cyc); + assume(!i_wb_stb); + end + end +endmodule diff --git a/cores/wb/wb_timer/formal/wb_timer.sby b/cores/wb/wb_timer/formal/wb_timer.sby new file mode 100644 index 0000000..be55a8b --- /dev/null +++ b/cores/wb/wb_timer/formal/wb_timer.sby @@ -0,0 +1,23 @@ +[tasks] +prove +cover +bmc + +[options] +bmc: mode bmc +bmc: depth 50 +cover: mode cover +cover: depth 50 +prove: mode prove + +[engines] +bmc: smtbmc yices +cover: smtbmc yices +prove: abc pdr + +[script] +{{"-formal"|gen_reads}} +prep -top {{top_level}} + +[files] +{{files}} diff --git a/cores/wb/wb_timer/wb_timer.core b/cores/wb/wb_timer/wb_timer.core index 628aff2..47503f9 100644 --- a/cores/wb/wb_timer/wb_timer.core +++ b/cores/wb/wb_timer/wb_timer.core @@ -8,6 +8,16 @@ filesets: files: - rtl/wb_timer.v file_type: verilogSource + formal_rtl: + depend: + - joppeb:wb:formal_checker + files: + - formal/formal_wb_timer.v + file_type: verilogSource + formal_cfg: + files: + - formal/wb_timer.sby + file_type: sbyConfigTemplate targets: default: @@ -17,6 +27,16 @@ targets: parameters: - WIDTH - DIVIDER + formal: + default_tool: symbiyosys + filesets: + - rtl + - formal_rtl + - formal_cfg + toplevel: formal_wb_timer + parameters: + - WIDTH + - DIVIDER parameters: WIDTH: From abe0668787357f68ce25db37144588a838c2cb62 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sun, 1 Mar 2026 17:16:44 +0100 Subject: [PATCH 07/12] new timer --- .gitignore | 3 +- .gitmodules | 2 +- cores/wb/check_formal.sh => check_formal.sh | 4 + cores/system/mcu/rtl/mcu_peripherals.v | 8 +- cores/system/test/rtl/toplevel.v | 5 +- cores/system/test/sw/sweep/sweep.c | 16 +- cores/system/test/tb/tb_toplevel.v | 37 +++++ cores/system/test/test.core | 13 +- .../formal/formal_wb_master_checker.v | 56 +++---- .../formal/formal_wb_slave_checker.v | 66 ++++---- cores/wb/wb_gpio/formal/wb_gpio/config.sby | 13 -- cores/wb/wb_gpio/formal/wb_gpio/logfile.txt | 2 - cores/wb/wb_timer/formal/formal_wb_timer.v | 14 +- cores/wb/wb_timer/formal/wb_timer.sby | 2 +- cores/wb/wb_timer/rtl/wb_timer.v | 118 +++++++++------ cores/wb/wb_timer/tb/tb_wb_timer.v | 143 ++++++++++++++++++ cores/wb/wb_timer/wb_timer.core | 24 +-- fusesoc_libraries/serv | 2 +- xilinx_fusesoc.sh | 8 + 19 files changed, 381 insertions(+), 155 deletions(-) rename cores/wb/check_formal.sh => check_formal.sh (97%) create mode 100644 cores/system/test/tb/tb_toplevel.v delete mode 100644 cores/wb/wb_gpio/formal/wb_gpio/config.sby delete mode 100644 cores/wb/wb_gpio/formal/wb_gpio/logfile.txt create mode 100644 cores/wb/wb_timer/tb/tb_wb_timer.v create mode 100755 xilinx_fusesoc.sh diff --git a/.gitignore b/.gitignore index d163863..3c0160d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build/ \ No newline at end of file +build/ +out/ diff --git a/.gitmodules b/.gitmodules index 9552b04..9f09b32 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "fusesoc_libraries/serv"] path = fusesoc_libraries/serv - url = https://github.com/Jojojoppe/serv/tree/xilinx_ise + url = git@github.com:Jojojoppe/serv.git [submodule "fusesoc_libraries/fusesoc-cores"] path = fusesoc_libraries/fusesoc-cores url = https://github.com/fusesoc/fusesoc-cores diff --git a/cores/wb/check_formal.sh b/check_formal.sh similarity index 97% rename from cores/wb/check_formal.sh rename to check_formal.sh index 4df22a7..9155dd7 100755 --- a/cores/wb/check_formal.sh +++ b/check_formal.sh @@ -9,6 +9,10 @@ joppeb:wb:jtag_wb_bridge joppeb:wb:wb_timer " +if [ -n "$1" ]; then + CORES="$1" +fi + # Add or remove formal tasks here. TASKS=" bmc diff --git a/cores/system/mcu/rtl/mcu_peripherals.v b/cores/system/mcu/rtl/mcu_peripherals.v index 771445e..d5ea5b9 100644 --- a/cores/system/mcu/rtl/mcu_peripherals.v +++ b/cores/system/mcu/rtl/mcu_peripherals.v @@ -44,7 +44,9 @@ module mcu_peripherals ( wire [31:0] gpio_wbs_dat_r; wire gpio_wbs_ack; + wire [31:0] timer_wbs_adr = wbs_adr[1*32 +: 32]; wire [31:0] timer_wbs_dat_w = wbs_dat_w[1*32 +: 32]; + wire [3:0] timer_wbs_sel = wbs_sel[1*4 +: 4]; wire timer_wbs_we = wbs_we[1]; wire timer_wbs_cyc = wbs_cyc[1]; wire timer_wbs_stb = wbs_stb[1]; @@ -108,12 +110,16 @@ module mcu_peripherals ( assign wbs_dat_r[0*32 +: 32] = gpio_wbs_dat_r; assign wbs_ack[0] = gpio_wbs_ack; - wb_countdown_timer timer ( + wb_countdown_timer #( + .address(TIMER_BASE_ADDR) + ) timer ( .i_clk(i_clk), .i_rst(i_rst), .o_irq(o_timer_irq), + .i_wb_adr(timer_wbs_adr), .i_wb_dat(timer_wbs_dat_w), .o_wb_dat(timer_wbs_dat_r), + .i_wb_sel(timer_wbs_sel), .i_wb_we(timer_wbs_we), .i_wb_cyc(timer_wbs_cyc), .i_wb_stb(timer_wbs_stb), diff --git a/cores/system/test/rtl/toplevel.v b/cores/system/test/rtl/toplevel.v index 093a97d..e14c9f8 100644 --- a/cores/system/test/rtl/toplevel.v +++ b/cores/system/test/rtl/toplevel.v @@ -1,7 +1,8 @@ `timescale 1ns/1ps module toplevel #( - parameter sim = 0 + parameter sim = 0, + parameter memfile = "sweep.hex" )( input wire aclk, input wire aresetn, @@ -57,7 +58,7 @@ module toplevel #( wire test; mcu #( - .memfile("../sw/sweep/sweep.hex"), + .memfile(memfile), .sim(sim), .jtag(1) ) mcu ( diff --git a/cores/system/test/sw/sweep/sweep.c b/cores/system/test/sw/sweep/sweep.c index 038db42..447ba27 100644 --- a/cores/system/test/sw/sweep/sweep.c +++ b/cores/system/test/sw/sweep/sweep.c @@ -6,7 +6,9 @@ static volatile uint32_t * const LEDS = (volatile uint32_t *)(GPIO_BASE+4) static volatile uint32_t * const LEDGR = (volatile uint32_t *)(GPIO_BASE+8); #define TIMER_BASE 0x40010000u -static volatile uint32_t * const TIMER = (volatile uint32_t *)(TIMER_BASE+0); +static volatile uint32_t * const TIMER_CNT = (volatile uint32_t *)(TIMER_BASE+0); +static volatile uint32_t * const TIMER_LD = (volatile uint32_t *)(TIMER_BASE+4); +static volatile uint32_t * const TIMER_ACK = (volatile uint32_t *)(TIMER_BASE+8); #define MSTATUS_MIE (1u << 3) #define MIE_MTIE (1u << 7) @@ -25,21 +27,21 @@ static inline void irq_init() { } void timer_isr(){ - static int set = 0; - *TIMER = 1840000*8; + *TIMER_ACK = 1; *LEDGR = ~(*LEDGR); } void main(){ irq_init(); - *LEDGR = 3; - *TIMER = 1840000*2; + *LEDGR = 1; + *TIMER_LD = 2 * 15000000/1000; for(;;){ - for(int i=1000; i<10000; i++){ + for(int i=1000; i<10000; i+=10){ *R_FREQ = i; - for(int j=0; j<80; j++) asm volatile("nop"); + *LEDS = i>>4; + // for(int j=0; j<80; j++) asm volatile("nop"); } } } \ No newline at end of file diff --git a/cores/system/test/tb/tb_toplevel.v b/cores/system/test/tb/tb_toplevel.v new file mode 100644 index 0000000..0be568b --- /dev/null +++ b/cores/system/test/tb/tb_toplevel.v @@ -0,0 +1,37 @@ +`timescale 1ns/1ps + +module tb_toplevel; + reg aclk; + reg aresetn; + wire led_green; + wire led_red; + wire [5:0] r2r; + wire [7:0] LED; + + toplevel #( + .sim(1) + ) dut ( + .aclk(aclk), + .aresetn(aresetn), + .led_green(led_green), + .led_red(led_red), + .r2r(r2r), + .LED(LED) + ); + + initial aclk = 1'b0; + always #33.33 aclk = ~aclk; + + initial begin + $dumpfile("toplevel.vcd"); + $dumpvars(1, tb_toplevel); + + aresetn = 1'b0; + #100; + aresetn = 1'b1; + + #10_000_000; + + $finish; + end +endmodule diff --git a/cores/system/test/test.core b/cores/system/test/test.core index 0702eae..31c1f11 100644 --- a/cores/system/test/test.core +++ b/cores/system/test/test.core @@ -13,10 +13,14 @@ filesets: files: - rtl/toplevel.v file_type: verilogSource + tb: + files: + - tb/tb_toplevel.v + file_type: verilogSource sw: files: - - sw/sweep/sweep.hex + - sw/sweep/sweep.hex : {copyto : sweep.hex} file_type: user mimas: @@ -29,6 +33,13 @@ targets: filesets: - rtl toplevel: toplevel + sim: + default_tool: icarus + filesets: + - rtl + - sw + - tb + toplevel: tb_toplevel mimas: filesets: diff --git a/cores/wb/formal_checker/formal/formal_wb_master_checker.v b/cores/wb/formal_checker/formal/formal_wb_master_checker.v index 68dc7c6..13f4ae9 100644 --- a/cores/wb/formal_checker/formal/formal_wb_master_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_master_checker.v @@ -22,14 +22,14 @@ module formal_wb_master_checker ( // A1: Slave ACK must correspond to either a same-cycle or previous-cycle request if(o_wb_ack) - assume( + A1: 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); + A2: assume(!o_wb_ack); // A3: Once STB has been low for a full cycle, slave ACK must be low if( @@ -37,19 +37,19 @@ module formal_wb_master_checker ( !$past(i_wb_stb) && !i_wb_stb ) - assume(!o_wb_ack); + A3: 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); + R1: assert(!i_wb_cyc); + R2: assert(!i_wb_stb); end - // R2: STB never high without CYC + // R3: STB never high without CYC if(i_wb_stb) - assert(i_wb_cyc); + R3: assert(i_wb_cyc); - // R3: Once a request starts, hold it stable until the slave responds + // R4-R9: Once a request starts, hold it stable until the slave responds if( f_past_valid && !$past(i_rst || i_wb_rst) && @@ -57,40 +57,40 @@ module formal_wb_master_checker ( !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)); + R4: assert(i_wb_cyc); + R5: assert(i_wb_stb); + R6: assert(i_wb_adr == $past(i_wb_adr)); + R7: assert(i_wb_dat == $past(i_wb_dat)); + R8: assert(i_wb_sel == $past(i_wb_sel)); + R9: assert(i_wb_we == $past(i_wb_we)); end - // R4: Once CYC is low, STB must also be low + // R10: Once CYC is low, STB must also be low if(!i_wb_cyc) - assert(!i_wb_stb); + R10: assert(!i_wb_stb); - // C0: We eventually initiate a request - cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb); + // C1: We eventually initiate a request + C1: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb); - // C1: We eventually get an ACK during an active request - cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); + // C2: We eventually get an ACK during an active request + C2: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); - // C2: A delayed ACK occurs for a request issued in a previous cycle. + // C3: A delayed ACK occurs for a request issued in a previous cycle. // This does not require the request to drop in the ACK cycle. - cover(f_past_valid && !i_rst && !i_wb_rst && + C3: cover(f_past_valid && !i_rst && !i_wb_rst && $past(i_wb_cyc && i_wb_stb) && o_wb_ack); // C4: A “wait state” happens: request asserted, no ACK for at least 1 cycle - cover(f_past_valid && !i_rst && !i_wb_rst && + C4: cover(f_past_valid && !i_rst && !i_wb_rst && $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && (i_wb_cyc && i_wb_stb && !o_wb_ack)); - // C5: Read and write both occur (even if only once each) - cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && !i_wb_we); - cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && i_wb_we); + // C5-C6: Read and write both occur (even if only once each) + C5: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && !i_wb_we); + C6: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && i_wb_we); - // C6: A transfer completes and the master drops CYC sometime after - cover(f_past_valid && !i_rst && !i_wb_rst && + // C7: A transfer completes and the master drops CYC sometime after + C7: cover(f_past_valid && !i_rst && !i_wb_rst && $past(i_wb_cyc && i_wb_stb && o_wb_ack) && !i_wb_cyc); end diff --git a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v index 866e730..b1217fb 100644 --- a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v @@ -1,7 +1,8 @@ `timescale 1ns/1ps module formal_wb_slave_checker #( - parameter combinatorial_ack = 0 + parameter combinatorial_ack = 0, + parameter expect_wait_state = 0 ) ( input wire i_clk, input wire i_rst, @@ -24,38 +25,38 @@ module formal_wb_slave_checker #( // A1: Reset forces cyc=0, stb=0 if (i_rst) begin - assume(!i_wb_cyc); - assume(!i_wb_stb); + A1: assume(!i_wb_cyc); + A2: assume(!i_wb_stb); end - // A2: std->cyc, stb never high without cyc + // A3: std->cyc, stb never high without cyc if(i_wb_stb) - assume(i_wb_cyc); + A3: assume(i_wb_cyc); - // A3: once a request starts, hold it stable until the slave responds + // A4-A9: 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)); + A4: assume(i_wb_cyc); + A5: assume(i_wb_stb); + A6: assume(i_wb_adr == $past(i_wb_adr)); + A7: assume(i_wb_dat == $past(i_wb_dat)); + A8: assume(i_wb_sel == $past(i_wb_sel)); + A9: 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( + R1: 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); + R2: 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); + R3: assert(!o_wb_ack); // R4: once STB has been dropped for a full cycle, ACK must be low if( @@ -63,34 +64,35 @@ module formal_wb_slave_checker #( !$past(i_wb_stb) && !i_wb_stb ) - assert(!o_wb_ack); + R4: assert(!o_wb_ack); - // C0: A request occurs at all - cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb); + // C1: A request occurs at all + C1: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb); - // C1: A request with write and with read occur - cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && i_wb_we); - cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && !i_wb_we); + // C2-C3: A request with write and with read occur + C2: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && i_wb_we); + C3: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && !i_wb_we); - // C2: ACK happens during a request (basic progress) - cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); + // C4: ACK happens during a request (basic progress) + C4: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); - // C3: Exercise the expected ACK timing style for this slave. + // C5-C7: Exercise the expected ACK timing style for this slave. if (combinatorial_ack) begin - cover(f_past_valid && !i_rst && !i_wb_rst && + C5: cover(f_past_valid && !i_rst && !i_wb_rst && (i_wb_cyc && i_wb_stb) && o_wb_ack); end else begin - cover(f_past_valid && !i_rst && !i_wb_rst && + C6: cover(f_past_valid && !i_rst && !i_wb_rst && $past(i_wb_cyc && i_wb_stb) && o_wb_ack); - // C4: Wait-state behavior for registered/non-zero-wait slaves. - cover(f_past_valid && !i_rst && !i_wb_rst && - $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && - (i_wb_cyc && i_wb_stb && !o_wb_ack)); + // C7: Optional wait-state behavior for slaves that intentionally stall. + if (expect_wait_state) + C7: cover(f_past_valid && !i_rst && !i_wb_rst && + $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && + (i_wb_cyc && i_wb_stb && !o_wb_ack)); end - // C5: Master ends a cycle (CYC drops) after at least one request - cover(f_past_valid && !i_rst && !i_wb_rst && + // C8: Master ends a cycle (CYC drops) after at least one request + C8: cover(f_past_valid && !i_rst && !i_wb_rst && $past(i_wb_cyc && i_wb_stb) && !i_wb_cyc); end diff --git a/cores/wb/wb_gpio/formal/wb_gpio/config.sby b/cores/wb/wb_gpio/formal/wb_gpio/config.sby deleted file mode 100644 index 0fbf6d2..0000000 --- a/cores/wb/wb_gpio/formal/wb_gpio/config.sby +++ /dev/null @@ -1,13 +0,0 @@ -[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}} diff --git a/cores/wb/wb_gpio/formal/wb_gpio/logfile.txt b/cores/wb/wb_gpio/formal/wb_gpio/logfile.txt deleted file mode 100644 index 5bc85e5..0000000 --- a/cores/wb/wb_gpio/formal/wb_gpio/logfile.txt +++ /dev/null @@ -1,2 +0,0 @@ -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}}'. diff --git a/cores/wb/wb_timer/formal/formal_wb_timer.v b/cores/wb/wb_timer/formal/formal_wb_timer.v index 848c8e3..ee82a09 100644 --- a/cores/wb/wb_timer/formal/formal_wb_timer.v +++ b/cores/wb/wb_timer/formal/formal_wb_timer.v @@ -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), diff --git a/cores/wb/wb_timer/formal/wb_timer.sby b/cores/wb/wb_timer/formal/wb_timer.sby index be55a8b..f6d6f5b 100644 --- a/cores/wb/wb_timer/formal/wb_timer.sby +++ b/cores/wb/wb_timer/formal/wb_timer.sby @@ -13,7 +13,7 @@ prove: mode prove [engines] bmc: smtbmc yices cover: smtbmc yices -prove: abc pdr +prove: smtbmc yices [script] {{"-formal"|gen_reads}} diff --git a/cores/wb/wb_timer/rtl/wb_timer.v b/cores/wb/wb_timer/rtl/wb_timer.v index ac14586..0370c3a 100644 --- a/cores/wb/wb_timer/rtl/wb_timer.v +++ b/cores/wb/wb_timer/rtl/wb_timer.v @@ -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 \ No newline at end of file +endmodule diff --git a/cores/wb/wb_timer/tb/tb_wb_timer.v b/cores/wb/wb_timer/tb/tb_wb_timer.v new file mode 100644 index 0000000..0c38307 --- /dev/null +++ b/cores/wb/wb_timer/tb/tb_wb_timer.v @@ -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 diff --git a/cores/wb/wb_timer/wb_timer.core b/cores/wb/wb_timer/wb_timer.core index 47503f9..91a294b 100644 --- a/cores/wb/wb_timer/wb_timer.core +++ b/cores/wb/wb_timer/wb_timer.core @@ -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 diff --git a/fusesoc_libraries/serv b/fusesoc_libraries/serv index 4999793..74a7f73 160000 --- a/fusesoc_libraries/serv +++ b/fusesoc_libraries/serv @@ -1 +1 @@ -Subproject commit 4999793a48485e2104155048859dc8506b719a50 +Subproject commit 74a7f73d3198f17bebf5754cb94e72dc30b0103e diff --git a/xilinx_fusesoc.sh b/xilinx_fusesoc.sh new file mode 100755 index 0000000..0e9bfd4 --- /dev/null +++ b/xilinx_fusesoc.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +. /opt/Xilinx/14.7/ISE_DS/settings64.sh /opt/Xilinx/14.7/ISE_DS + +echo fusesoc "$@" +exec fusesoc "$@" From 5b940758b633e77311b8e487ccd1ab8812ac6813 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sun, 1 Mar 2026 21:00:57 +0100 Subject: [PATCH 08/12] Added formal verification set to timer internally --- check_formal.sh | 2 +- cores/wb/wb_timer/formal/formal_wb_timer.v | 6 +- cores/wb/wb_timer/rtl/wb_timer.v | 90 +++++++++++++++++++++- 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/check_formal.sh b/check_formal.sh index 9155dd7..302fbf3 100755 --- a/check_formal.sh +++ b/check_formal.sh @@ -55,7 +55,7 @@ $label" $label" printf 'Result: FAIL (%s)\n' "$label" printf 'Captured log for %s:\n' "$label" - cat "$log_file" | grep summary + cat "$log_file" #| grep summary rm -f "$log_file" return 1 fi diff --git a/cores/wb/wb_timer/formal/formal_wb_timer.v b/cores/wb/wb_timer/formal/formal_wb_timer.v index ee82a09..0a1250c 100644 --- a/cores/wb/wb_timer/formal/formal_wb_timer.v +++ b/cores/wb/wb_timer/formal/formal_wb_timer.v @@ -17,7 +17,9 @@ module formal_wb_timer; assign i_wb_rst = 1'b0; - wb_countdown_timer dut ( + wb_countdown_timer #( + .FORMAL(1) + ) dut ( .i_clk(i_clk), .i_rst(i_rst), .o_irq(o_irq), @@ -32,7 +34,7 @@ module formal_wb_timer; ); formal_wb_slave_checker #( - .combinatorial_ack(0) + .combinatorial_ack(0), ) wb_checker ( .i_clk(i_clk), .i_rst(i_rst), diff --git a/cores/wb/wb_timer/rtl/wb_timer.v b/cores/wb/wb_timer/rtl/wb_timer.v index 0370c3a..ff37dbc 100644 --- a/cores/wb/wb_timer/rtl/wb_timer.v +++ b/cores/wb/wb_timer/rtl/wb_timer.v @@ -1,7 +1,8 @@ `timescale 1ns/1ps module wb_countdown_timer #( - parameter address = 32'h00000000 // Base address of peripheral + parameter address = 32'h00000000, // Base address of peripheral + parameter FORMAL = 0 )( input wire i_clk, input wire i_rst, @@ -97,4 +98,91 @@ module wb_countdown_timer #( end end + // Formal verification + if(FORMAL) begin + reg f_past_valid = 1'b0; + wire cnt_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h0); + wire pld_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h4); + wire ack_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h8); + reg [31:0] past_counter; + + always @(posedge i_clk) begin + f_past_valid <= 1'b1; + past_counter <= counter; + + // R1: Reset clears all timer state on the following cycle. + // Check counter, preload, wb_ack, o_wb_dat, irq_fired, + // counter_started, counter_running, and prev_counter_running. + if(f_past_valid && $past(i_rst)) begin + R1s1: assert(counter==0); + R1s2: assert(preload==0); + R1s3: assert(!wb_ack); + R1s4: assert(o_wb_dat==0); + R1s5: assert(!irq_fired); + R1s6: assert(!counter_started); + R1s7: assert(!counter_running); + end + + // R2: irq_fired is sticky until reset or a write to BASE+8 clears it. + // -> if last cycle was irq and last cycle was not reset and not ack write then now still irq + if(f_past_valid && $past(!i_rst) && $past(irq_fired) && $past(!ack_write)) + R2: assert(irq_fired); + + // R3: A write to BASE+8 clears irq_fired on the following cycle. + // -> if last cycle was ack write and irq was high it must be low now + if(f_past_valid && $past(!i_rst) && $past(irq_fired) && $past(ack_write)) + R3: assert(!irq_fired); + + // R4: While the timer is running and no counter write overrides it, + // counter decrements by exactly one each cycle. + if(f_past_valid && $past(!i_rst) && $past(counter>1) && $past(counter_started) && $past(!cnt_write)) + R4: assert(counter == $past(counter)-1); + + // R5: When counter reaches zero with preload > 0 and the timer is started, + // the counter reloads from preload. + if(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload>0) && $past(counter_started) && $past(!cnt_write)) + R5: assert(counter == $past(preload)); + + // R6: When counter == 0 and preload == 0, the timer stops + // (counter_started deasserts unless a write rearms it). + if(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload==0) && $past(!cnt_write) && $past(!pld_write)) begin + R6s1: assert(counter==0); + R6s2: assert(counter_started==0); + end + + // R7: A write to BASE+0 or BASE+4 arms the timer + // (counter_started asserts on the following cycle). + if(f_past_valid && $past(!i_rst) && ($past(cnt_write) || $past(pld_write))) + R7: assert(counter_started==1); + + // R8: o_irq always reflects irq_fired. + R8: assert(o_irq == irq_fired); + + // R9: Interrupt only fired after counter was 0 two clock cycles ago + if(f_past_valid && $past(!i_rst) && $past(!irq_fired) && irq_fired) + R9: assert($past(past_counter) == 0); + + // C1: Cover a counter write that starts the timer. + C1s1: cover(f_past_valid && !i_rst && cnt_write); + if(f_past_valid && $past(!i_rst) && $past(cnt_write)) + C1s2: cover(counter_started); + + // C2: Cover a preload write. + C2: cover(f_past_valid && !i_rst && pld_write); + + // C3: Cover the counter decrementing at least once. + C3: cover(f_past_valid && $past(!i_rst) && $past(counter)==counter-1 && $past(!cnt_write) && $past(!pld_write)); + + // C4: Cover the counter reloading from preload. + C4: cover(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload>0) && counter>0 && $past(!cnt_write) && $past(!pld_write)); + + // C5: Cover irq_fired asserting when the timer expires. + C5: cover(f_past_valid && $past(!i_rst) && $past(!irq_fired) && irq_fired && $past(counter==0)); + + // C6: Cover irq_fired being cleared by a write to BASE+8. + C6: cover(f_past_valid && $past(!i_rst) && $past(irq_fired) && !irq_fired && $past(ack_write)); + + end + end; + endmodule From a6a5c6ea3f18363eb1082dfd3e821daa5997bf8a Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sun, 1 Mar 2026 21:11:08 +0100 Subject: [PATCH 09/12] Made timer synthesizable --- cores/wb/wb_timer/formal/formal_wb_timer.v | 4 +- cores/wb/wb_timer/rtl/wb_timer.v | 167 ++++++++++----------- cores/wb/wb_timer/wb_timer.core | 5 + 3 files changed, 89 insertions(+), 87 deletions(-) diff --git a/cores/wb/wb_timer/formal/formal_wb_timer.v b/cores/wb/wb_timer/formal/formal_wb_timer.v index 0a1250c..931dd27 100644 --- a/cores/wb/wb_timer/formal/formal_wb_timer.v +++ b/cores/wb/wb_timer/formal/formal_wb_timer.v @@ -17,9 +17,7 @@ module formal_wb_timer; assign i_wb_rst = 1'b0; - wb_countdown_timer #( - .FORMAL(1) - ) dut ( + wb_countdown_timer dut ( .i_clk(i_clk), .i_rst(i_rst), .o_irq(o_irq), diff --git a/cores/wb/wb_timer/rtl/wb_timer.v b/cores/wb/wb_timer/rtl/wb_timer.v index ff37dbc..87f559c 100644 --- a/cores/wb/wb_timer/rtl/wb_timer.v +++ b/cores/wb/wb_timer/rtl/wb_timer.v @@ -1,8 +1,7 @@ `timescale 1ns/1ps module wb_countdown_timer #( - parameter address = 32'h00000000, // Base address of peripheral - parameter FORMAL = 0 + parameter address = 32'h00000000 // Base address of peripheral )( input wire i_clk, input wire i_rst, @@ -98,91 +97,91 @@ module wb_countdown_timer #( end end +`ifdef FORMAL // Formal verification - if(FORMAL) begin - reg f_past_valid = 1'b0; - wire cnt_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h0); - wire pld_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h4); - wire ack_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h8); - reg [31:0] past_counter; + reg f_past_valid = 1'b0; + wire cnt_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h0); + wire pld_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h4); + wire ack_write = i_wb_cyc && i_wb_stb && i_wb_we && (i_wb_adr[3:0] == 4'h8); + reg [31:0] past_counter; - always @(posedge i_clk) begin - f_past_valid <= 1'b1; - past_counter <= counter; - - // R1: Reset clears all timer state on the following cycle. - // Check counter, preload, wb_ack, o_wb_dat, irq_fired, - // counter_started, counter_running, and prev_counter_running. - if(f_past_valid && $past(i_rst)) begin - R1s1: assert(counter==0); - R1s2: assert(preload==0); - R1s3: assert(!wb_ack); - R1s4: assert(o_wb_dat==0); - R1s5: assert(!irq_fired); - R1s6: assert(!counter_started); - R1s7: assert(!counter_running); - end - - // R2: irq_fired is sticky until reset or a write to BASE+8 clears it. - // -> if last cycle was irq and last cycle was not reset and not ack write then now still irq - if(f_past_valid && $past(!i_rst) && $past(irq_fired) && $past(!ack_write)) - R2: assert(irq_fired); - - // R3: A write to BASE+8 clears irq_fired on the following cycle. - // -> if last cycle was ack write and irq was high it must be low now - if(f_past_valid && $past(!i_rst) && $past(irq_fired) && $past(ack_write)) - R3: assert(!irq_fired); - - // R4: While the timer is running and no counter write overrides it, - // counter decrements by exactly one each cycle. - if(f_past_valid && $past(!i_rst) && $past(counter>1) && $past(counter_started) && $past(!cnt_write)) - R4: assert(counter == $past(counter)-1); - - // R5: When counter reaches zero with preload > 0 and the timer is started, - // the counter reloads from preload. - if(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload>0) && $past(counter_started) && $past(!cnt_write)) - R5: assert(counter == $past(preload)); - - // R6: When counter == 0 and preload == 0, the timer stops - // (counter_started deasserts unless a write rearms it). - if(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload==0) && $past(!cnt_write) && $past(!pld_write)) begin - R6s1: assert(counter==0); - R6s2: assert(counter_started==0); - end - - // R7: A write to BASE+0 or BASE+4 arms the timer - // (counter_started asserts on the following cycle). - if(f_past_valid && $past(!i_rst) && ($past(cnt_write) || $past(pld_write))) - R7: assert(counter_started==1); - - // R8: o_irq always reflects irq_fired. - R8: assert(o_irq == irq_fired); - - // R9: Interrupt only fired after counter was 0 two clock cycles ago - if(f_past_valid && $past(!i_rst) && $past(!irq_fired) && irq_fired) - R9: assert($past(past_counter) == 0); - - // C1: Cover a counter write that starts the timer. - C1s1: cover(f_past_valid && !i_rst && cnt_write); - if(f_past_valid && $past(!i_rst) && $past(cnt_write)) - C1s2: cover(counter_started); - - // C2: Cover a preload write. - C2: cover(f_past_valid && !i_rst && pld_write); - - // C3: Cover the counter decrementing at least once. - C3: cover(f_past_valid && $past(!i_rst) && $past(counter)==counter-1 && $past(!cnt_write) && $past(!pld_write)); - - // C4: Cover the counter reloading from preload. - C4: cover(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload>0) && counter>0 && $past(!cnt_write) && $past(!pld_write)); - - // C5: Cover irq_fired asserting when the timer expires. - C5: cover(f_past_valid && $past(!i_rst) && $past(!irq_fired) && irq_fired && $past(counter==0)); - - // C6: Cover irq_fired being cleared by a write to BASE+8. - C6: cover(f_past_valid && $past(!i_rst) && $past(irq_fired) && !irq_fired && $past(ack_write)); + always @(posedge i_clk) begin + f_past_valid <= 1'b1; + past_counter <= counter; + // R1: Reset clears all timer state on the following cycle. + // Check counter, preload, wb_ack, o_wb_dat, irq_fired, + // counter_started, counter_running, and prev_counter_running. + if(f_past_valid && $past(i_rst)) begin + R1s1: assert(counter==0); + R1s2: assert(preload==0); + R1s3: assert(!wb_ack); + R1s4: assert(o_wb_dat==0); + R1s5: assert(!irq_fired); + R1s6: assert(!counter_started); + R1s7: assert(!counter_running); end - end; + + // R2: irq_fired is sticky until reset or a write to BASE+8 clears it. + // -> if last cycle was irq and last cycle was not reset and not ack write then now still irq + if(f_past_valid && $past(!i_rst) && $past(irq_fired) && $past(!ack_write)) + R2: assert(irq_fired); + + // R3: A write to BASE+8 clears irq_fired on the following cycle. + // -> if last cycle was ack write and irq was high it must be low now + if(f_past_valid && $past(!i_rst) && $past(irq_fired) && $past(ack_write)) + R3: assert(!irq_fired); + + // R4: While the timer is running and no counter write overrides it, + // counter decrements by exactly one each cycle. + if(f_past_valid && $past(!i_rst) && $past(counter>1) && $past(counter_started) && $past(!cnt_write)) + R4: assert(counter == $past(counter)-1); + + // R5: When counter reaches zero with preload > 0 and the timer is started, + // the counter reloads from preload. + if(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload>0) && $past(counter_started) && $past(!cnt_write)) + R5: assert(counter == $past(preload)); + + // R6: When counter == 0 and preload == 0, the timer stops + // (counter_started deasserts unless a write rearms it). + if(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload==0) && $past(!cnt_write) && $past(!pld_write)) begin + R6s1: assert(counter==0); + R6s2: assert(counter_started==0); + end + + // R7: A write to BASE+0 or BASE+4 arms the timer + // (counter_started asserts on the following cycle). + if(f_past_valid && $past(!i_rst) && ($past(cnt_write) || $past(pld_write))) + R7: assert(counter_started==1); + + // R8: o_irq always reflects irq_fired. + R8: assert(o_irq == irq_fired); + + // R9: Interrupt only fired after counter was 0 two clock cycles ago + if(f_past_valid && $past(!i_rst) && $past(!irq_fired) && irq_fired) + R9: assert($past(past_counter) == 0); + + // C1: Cover a counter write that starts the timer. + C1s1: cover(f_past_valid && !i_rst && cnt_write); + if(f_past_valid && $past(!i_rst) && $past(cnt_write)) + C1s2: cover(counter_started); + + // C2: Cover a preload write. + C2: cover(f_past_valid && !i_rst && pld_write); + + // C3: Cover the counter decrementing at least once. + C3: cover(f_past_valid && $past(!i_rst) && $past(counter)==counter-1 && $past(!cnt_write) && $past(!pld_write)); + + // C4: Cover the counter reloading from preload. + C4: cover(f_past_valid && $past(!i_rst) && $past(counter==0) && $past(preload>0) && counter>0 && $past(!cnt_write) && $past(!pld_write)); + + // C5: Cover irq_fired asserting when the timer expires. + C5: cover(f_past_valid && $past(!i_rst) && $past(!irq_fired) && irq_fired && $past(counter==0)); + + // C6: Cover irq_fired being cleared by a write to BASE+8. + C6: cover(f_past_valid && $past(!i_rst) && $past(irq_fired) && !irq_fired && $past(ack_write)); + + end +`endif endmodule diff --git a/cores/wb/wb_timer/wb_timer.core b/cores/wb/wb_timer/wb_timer.core index 91a294b..193254f 100644 --- a/cores/wb/wb_timer/wb_timer.core +++ b/cores/wb/wb_timer/wb_timer.core @@ -45,9 +45,14 @@ targets: toplevel: formal_wb_timer parameters: - address + - FORMAL=true parameters: address: datatype: int description: Base address of register set paramtype: vlogparam + FORMAL: + datatype: bool + description: Enable in-module formal-only logic + paramtype: vlogdefine From 628972ccaa3b2e4cc7defbafefc5b40704bc357f Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Mon, 2 Mar 2026 13:48:30 +0100 Subject: [PATCH 10/12] New slave checker and updated gpio and gpio_banks --- cores/system/mcu/rtl/mcu_peripherals.v | 7 +- .../formal/formal_wb_slave_checker.v | 288 ++++++++++++------ cores/wb/wb_gpio/formal/formal_wb_gpio.v | 28 +- cores/wb/wb_gpio/rtl/wb_gpio.v | 95 +++--- cores/wb/wb_gpio/wb_gpio.core | 10 - .../formal/formal_wb_gpio_banks.v | 21 +- .../wb/wb_gpio_banks/formal/wb_gpio_banks.sby | 2 + cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v | 108 ++++--- cores/wb/wb_gpio_banks/wb_gpio_banks.core | 1 + cores/wb/wb_mem32/rtl/wb_mem32.v | 2 +- cores/wb/wb_timer/formal/formal_wb_timer.v | 4 +- cores/wb/wb_timer/rtl/wb_timer.v | 6 +- cores/wb/wb_timer/wb_timer.core | 7 - 13 files changed, 334 insertions(+), 245 deletions(-) diff --git a/cores/system/mcu/rtl/mcu_peripherals.v b/cores/system/mcu/rtl/mcu_peripherals.v index d5ea5b9..ec3d61b 100644 --- a/cores/system/mcu/rtl/mcu_peripherals.v +++ b/cores/system/mcu/rtl/mcu_peripherals.v @@ -99,7 +99,8 @@ module mcu_peripherals ( .i_wb_dat(gpio_wbs_dat_w), .i_wb_adr(gpio_wbs_adr), .i_wb_we(gpio_wbs_we), - .i_wb_stb(gpio_wbs_stb & gpio_wbs_cyc), + .i_wb_stb(gpio_wbs_stb), + .i_wb_cyc(gpio_wbs_cyc), .i_wb_sel(gpio_wbs_sel), .o_wb_rdt(gpio_wbs_dat_r), .o_wb_ack(gpio_wbs_ack), @@ -110,9 +111,7 @@ module mcu_peripherals ( assign wbs_dat_r[0*32 +: 32] = gpio_wbs_dat_r; assign wbs_ack[0] = gpio_wbs_ack; - wb_countdown_timer #( - .address(TIMER_BASE_ADDR) - ) timer ( + wb_countdown_timer timer ( .i_clk(i_clk), .i_rst(i_rst), .o_irq(o_timer_irq), diff --git a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v index b1217fb..479bdbc 100644 --- a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v @@ -1,101 +1,219 @@ -`timescale 1ns/1ps +// formal_wb_slave_checker.v +// +// Wishbone Classic slave-side protocol checker (plain Verilog). +// Use when your DUT is a *slave* and the bus/master is the environment. module formal_wb_slave_checker #( - parameter combinatorial_ack = 0, - parameter expect_wait_state = 0 + parameter OPT_USE_ERR = 0, + parameter OPT_USE_RTY = 0, + + // If 1: require slave to only assert responses when STB=1 (stricter profile). + // If 0: allow "ghost" responses with STB=0; termination still only counts when STB=1. + parameter OPT_STRICT_RESP_WITH_STB = 0, + + // If 1: require termination signals to be pulses (1 cycle). + // If 0: allow them to be held. + parameter OPT_PULSE_RESP = 1, + + // If 1: during reset require master to hold CYC/STB low (assumption; common convention). + parameter OPT_STRICT_RESET = 1, + + // If 1: assert read data stable while ACK is held (useful if you allow ACK-hold). + parameter OPT_ASSERT_RDATA_STABLE_DURING_ACK = 1, + + // Optional widths + parameter AW = 32, + parameter DW = 32, + parameter SW = 4 ) ( - 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 + input wire i_clk, + input wire i_rst, + input wire i_wb_rst, + + // Master -> slave + input wire i_wb_cyc, + input wire i_wb_stb, + input wire i_wb_we, + input wire [AW-1:0] i_wb_adr, + input wire [DW-1:0] i_wb_dat, + input wire [SW-1:0] i_wb_sel, + + // Slave -> master + input wire o_wb_ack, + input wire o_wb_err, + input wire o_wb_rty, + input wire [DW-1:0] o_wb_rdt ); + +`ifdef FORMAL + // ----------------------------- + // Reset combine + // ----------------------------- + wire rst_any; + assign rst_any = i_rst | i_wb_rst; + + // ----------------------------- + // Formal infrastructure + // ----------------------------- reg f_past_valid; - initial f_past_valid = 1'b0; - - always @(posedge i_clk) begin + always @(posedge i_clk) f_past_valid <= 1'b1; - // A1: Reset forces cyc=0, stb=0 - if (i_rst) begin - A1: assume(!i_wb_cyc); - A2: assume(!i_wb_stb); - end + wire f_resp_any; + assign f_resp_any = o_wb_ack + | (OPT_USE_ERR ? o_wb_err : 1'b0) + | (OPT_USE_RTY ? o_wb_rty : 1'b0); - // A3: std->cyc, stb never high without cyc - if(i_wb_stb) - A3: assume(i_wb_cyc); + // Real termination only counts when STB=1 + wire f_terminate; + assign f_terminate = i_wb_cyc && i_wb_stb && f_resp_any; - // A4-A9: 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 - A4: assume(i_wb_cyc); - A5: assume(i_wb_stb); - A6: assume(i_wb_adr == $past(i_wb_adr)); - A7: assume(i_wb_dat == $past(i_wb_dat)); - A8: assume(i_wb_sel == $past(i_wb_sel)); - A9: assume(i_wb_we == $past(i_wb_we)); - end + // ----------------------------- + // Outstanding request tracking (Classic) + // ----------------------------- + reg f_pending; + initial f_pending = 1'b0; - // R1: ACK must correspond to either a same-cycle or previous-cycle request - if(o_wb_ack) - R1: 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) - R2: 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)) - R3: 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 - ) - R4: assert(!o_wb_ack); - - // C1: A request occurs at all - C1: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb); - - // C2-C3: A request with write and with read occur - C2: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && i_wb_we); - C3: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && !i_wb_we); - - // C4: ACK happens during a request (basic progress) - C4: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); - - // C5-C7: Exercise the expected ACK timing style for this slave. - if (combinatorial_ack) begin - C5: cover(f_past_valid && !i_rst && !i_wb_rst && - (i_wb_cyc && i_wb_stb) && o_wb_ack); + always @(posedge i_clk or posedge rst_any) begin + if (rst_any) begin + f_pending <= 1'b0; + end else if (!i_wb_cyc) begin + f_pending <= 1'b0; end else begin - C6: cover(f_past_valid && !i_rst && !i_wb_rst && - $past(i_wb_cyc && i_wb_stb) && o_wb_ack); + if (!f_pending && i_wb_stb) + f_pending <= 1'b1; - // C7: Optional wait-state behavior for slaves that intentionally stall. - if (expect_wait_state) - C7: cover(f_past_valid && !i_rst && !i_wb_rst && - $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && - (i_wb_cyc && i_wb_stb && !o_wb_ack)); + if (f_terminate) + f_pending <= 1'b0; end - - // C8: Master ends a cycle (CYC drops) after at least one request - C8: cover(f_past_valid && !i_rst && !i_wb_rst && - $past(i_wb_cyc && i_wb_stb) && !i_wb_cyc); - end - wire unused = &{1'b0, o_wb_rdt}; -endmodule + // ----------------------------- + // Reset rules (recommended) + // ----------------------------- + always @(posedge i_clk) if (f_past_valid) begin + if (rst_any) begin + // R00: Monitor pending state must be cleared during reset + R00: assert(f_pending == 1'b0); + + if (OPT_STRICT_RESET) begin + // A00: During reset, assume the master is not attempting a bus cycle + A00: assume(!i_wb_cyc); + // A01: During reset, assume the master is not strobing + A01: assume(!i_wb_stb); + end + + // R01: Slave should not respond during reset (prevents silly traces) + R01: assert(!o_wb_ack); + if (OPT_USE_ERR) R02: assert(!o_wb_err); + if (OPT_USE_RTY) R03: assert(!o_wb_rty); + end + end + + // ----------------------------- + // Master/environment assumptions (Classic rules) + // ----------------------------- + always @(posedge i_clk) if (f_past_valid && !rst_any) begin + // A10: STB must imply CYC + if (i_wb_stb) begin + A10: assume(i_wb_cyc); + end + + // A11: While pending and not terminated, master holds CYC asserted + if ($past(f_pending) && !$past(f_terminate)) begin + A11: assume(i_wb_cyc); + end + + // A12: While pending and not terminated, master holds STB asserted + if ($past(f_pending) && !$past(f_terminate)) begin + A12: assume(i_wb_stb); + end + + // A13: Address stable while pending and not terminated + if ($past(f_pending) && !$past(f_terminate)) begin + A13: assume(i_wb_adr == $past(i_wb_adr)); + end + + // A14: WE stable while pending and not terminated + if ($past(f_pending) && !$past(f_terminate)) begin + A14: assume(i_wb_we == $past(i_wb_we)); + end + + // A15: SEL stable while pending and not terminated + if ($past(f_pending) && !$past(f_terminate)) begin + A15: assume(i_wb_sel == $past(i_wb_sel)); + end + + // A16: Write data stable while pending and not terminated + if ($past(f_pending) && !$past(f_terminate)) begin + A16: assume(i_wb_dat == $past(i_wb_dat)); + end + end + + // ----------------------------- + // Slave/DUT assertions (response sanity) + // ----------------------------- + always @(posedge i_clk) if (f_past_valid && !rst_any) begin + // R10: Any response must occur only during an active cycle + if (f_resp_any) begin + R10: assert(i_wb_cyc); + end + + // R11: Optional strict profile: response only when STB=1 + if (OPT_STRICT_RESP_WITH_STB && f_resp_any) begin + R11: assert(i_wb_stb); + end + + // R12-R14: Mutual exclusion of termination signals (recommended) + if (o_wb_ack) begin + if (OPT_USE_ERR) R12s1: assert(!o_wb_err); + if (OPT_USE_RTY) R12s2: assert(!o_wb_rty); + end + if (OPT_USE_ERR && o_wb_err) begin + R13s1: assert(!o_wb_ack); + if (OPT_USE_RTY) R13s2: assert(!o_wb_rty); + end + if (OPT_USE_RTY && o_wb_rty) begin + R14s1: assert(!o_wb_ack); + if (OPT_USE_ERR) R14s2: assert(!o_wb_err); + end + + // R15-R17: Optional pulse-only responses + if (OPT_PULSE_RESP) begin + if ($past(o_wb_ack)) begin + R15: assert(!o_wb_ack); + end + if (OPT_USE_ERR && $past(o_wb_err)) begin + R16: assert(!o_wb_err); + end + if (OPT_USE_RTY && $past(o_wb_rty)) begin + R17: assert(!o_wb_rty); + end + end + + // R18: A real termination (STB && response) should only happen when a request is pending. + if (i_wb_stb && f_resp_any) begin + R18: assert(f_pending || $past(f_pending)); + end + + // R19: Optional read-data stability while ACK is held (only relevant if ACK can be held) + if (OPT_ASSERT_RDATA_STABLE_DURING_ACK) begin + if (o_wb_ack && !i_wb_we && $past(o_wb_ack)) begin + R19: assert(o_wb_rdt == $past(o_wb_rdt)); + end + end + + // R20: If no cycle, no responses (strong sanity rule) + if (!i_wb_cyc) begin + R20: assert(!f_resp_any); + end + end + + // ----------------------------- + // Coverage: exercise the slave (useful witness traces) + // ----------------------------- + // TODO + +`endif +endmodule \ No newline at end of file diff --git a/cores/wb/wb_gpio/formal/formal_wb_gpio.v b/cores/wb/wb_gpio/formal/formal_wb_gpio.v index 5a4fdff..035e6b8 100644 --- a/cores/wb/wb_gpio/formal/formal_wb_gpio.v +++ b/cores/wb/wb_gpio/formal/formal_wb_gpio.v @@ -1,35 +1,29 @@ `timescale 1ns/1ps -module formal_wb_gpio #( - parameter [31:0] address = 32'h00000000 -); +module formal_wb_gpio; (* 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 i_wb_cyc; (* 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), + wb_gpio dut ( + .i_clk(i_wb_clk), + .i_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), .i_gpio(i_gpio), .o_wb_rdt(o_wb_rdt), .o_wb_ack(o_wb_ack), @@ -38,7 +32,7 @@ module formal_wb_gpio #( formal_wb_slave_checker wb_checker ( .i_clk(i_wb_clk), - .i_rst(i_rst), + .i_rst(i_wb_rst), .i_wb_rst(i_wb_rst), .i_wb_adr(i_wb_adr), .i_wb_dat(i_wb_dat), @@ -56,9 +50,13 @@ module formal_wb_gpio #( 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 + if(f_past_valid && + !i_wb_rst && $past(!i_wb_rst) && + o_wb_ack && + $past(i_wb_sel)==4'hf && i_wb_sel==4'hf && + $past(i_wb_cyc & i_wb_stb & !i_wb_we) && + (i_wb_cyc & i_wb_stb & !i_wb_we)) 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 diff --git a/cores/wb/wb_gpio/rtl/wb_gpio.v b/cores/wb/wb_gpio/rtl/wb_gpio.v index da8da52..bb59b8a 100644 --- a/cores/wb/wb_gpio/rtl/wb_gpio.v +++ b/cores/wb/wb_gpio/rtl/wb_gpio.v @@ -1,56 +1,53 @@ -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, +module wb_gpio ( + input wire i_clk, + input wire i_rst, - output reg [31:0] o_wb_rdt, - output reg o_wb_ack, - output reg [31:0] o_gpio + input wire [31:0] i_wb_adr, + input wire [31:0] i_wb_dat, + output reg [31:0] o_wb_rdt, + 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, + + input wire [31:0] i_gpio, + output wire [31:0] o_gpio ); + + // Registers + reg [31:0] gpo; + wire [31:0] gpi; + assign o_gpio = gpo; + assign gpi = i_gpio; - initial o_gpio <= 32'h00000000; - initial o_wb_rdt <= 32'h00000000; + reg wb_ack = 0; + assign o_wb_ack = wb_ack & i_wb_cyc & i_wb_stb; - wire addr_check; - assign addr_check = (i_wb_adr == address); + always @(posedge i_clk) begin + if(i_rst) begin + gpo <= 0; + wb_ack <= 0; + o_wb_rdt <= 0; + end else begin + // Ack generation + wb_ack <= i_wb_cyc & i_wb_stb & !wb_ack; - // 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 + // Read cycle + if(i_wb_cyc && i_wb_stb && !i_wb_we) begin + if(i_wb_sel[0]) o_wb_rdt[7:0] <= gpi[7:0]; + if(i_wb_sel[1]) o_wb_rdt[15:8] <= gpi[15:8]; + if(i_wb_sel[2]) o_wb_rdt[23:16] <= gpi[23:16]; + if(i_wb_sel[3]) o_wb_rdt[31:24] <= gpi[31:24]; + end + // write cycle + if(i_wb_cyc && i_wb_stb && i_wb_we) begin + if(i_wb_sel[0]) gpo[7:0] <= i_wb_dat[7:0]; + if(i_wb_sel[1]) gpo[15:8] <= i_wb_dat[15:8]; + if(i_wb_sel[2]) gpo[23:16] <= i_wb_dat[23:16]; + if(i_wb_sel[3]) gpo[31:24] <= i_wb_dat[31:24]; + end + end + end endmodule diff --git a/cores/wb/wb_gpio/wb_gpio.core b/cores/wb/wb_gpio/wb_gpio.core index 32e62e0..fa3cc43 100644 --- a/cores/wb/wb_gpio/wb_gpio.core +++ b/cores/wb/wb_gpio/wb_gpio.core @@ -25,8 +25,6 @@ targets: filesets: - rtl toplevel: wb_gpio - parameters: - - address formal: default_tool: symbiyosys @@ -35,11 +33,3 @@ targets: - formal_rtl - formal_cfg toplevel: formal_wb_gpio - parameters: - - address - -parameters: - address: - datatype: int - description: Wishbone address matched by this peripheral - paramtype: vlogparam diff --git a/cores/wb/wb_gpio_banks/formal/formal_wb_gpio_banks.v b/cores/wb/wb_gpio_banks/formal/formal_wb_gpio_banks.v index 505d4c1..658c7d1 100644 --- a/cores/wb/wb_gpio_banks/formal/formal_wb_gpio_banks.v +++ b/cores/wb/wb_gpio_banks/formal/formal_wb_gpio_banks.v @@ -1,31 +1,28 @@ `timescale 1ns/1ps module formal_wb_gpio_banks #( - parameter integer NUM_BANKS = 2, - parameter [31:0] BASE_ADDR = 32'h00000000 + parameter integer num_banks = 2, ); - (* gclk *) reg i_wb_clk; + (* 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 [NUM_BANKS*32-1:0] i_gpio; + (* anyseq *) reg [num_banks*32-1:0] i_gpio; wire [31:0] o_wb_rdt; wire o_wb_ack; - wire [NUM_BANKS*32-1:0] o_gpio; + wire [num_banks*32-1:0] o_gpio; wire i_wb_cyc; assign i_wb_cyc = i_wb_stb || o_wb_ack; wb_gpio_banks #( - .NUM_BANKS(NUM_BANKS), - .BASE_ADDR(BASE_ADDR) + .num_banks(num_banks) ) dut ( - .i_wb_clk(i_wb_clk), - .i_wb_rst(i_wb_rst), + .i_clk(i_clk), + .i_rst(i_rst), .i_wb_adr(i_wb_adr), .i_wb_dat(i_wb_dat), .i_wb_sel(i_wb_sel), @@ -38,9 +35,9 @@ module formal_wb_gpio_banks #( ); formal_wb_slave_checker wb_checker ( - .i_clk(i_wb_clk), + .i_clk(i_clk), .i_rst(i_rst), - .i_wb_rst(i_wb_rst), + .i_wb_rst(i_rst), .i_wb_adr(i_wb_adr), .i_wb_dat(i_wb_dat), .i_wb_sel(i_wb_sel), diff --git a/cores/wb/wb_gpio_banks/formal/wb_gpio_banks.sby b/cores/wb/wb_gpio_banks/formal/wb_gpio_banks.sby index be55a8b..e5382b5 100644 --- a/cores/wb/wb_gpio_banks/formal/wb_gpio_banks.sby +++ b/cores/wb/wb_gpio_banks/formal/wb_gpio_banks.sby @@ -16,8 +16,10 @@ cover: smtbmc yices prove: abc pdr [script] +read -formal clog2.vh {{"-formal"|gen_reads}} prep -top {{top_level}} [files] +src/joppeb_util_clog2_1.0/clog2.vh {{files}} diff --git a/cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v b/cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v index 48f4936..04476ec 100644 --- a/cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v +++ b/cores/wb/wb_gpio_banks/rtl/wb_gpio_banks.v @@ -1,63 +1,61 @@ -`default_nettype none +`include "clog2.vh" module wb_gpio_banks #( - parameter integer NUM_BANKS = 4, - parameter [31:0] BASE_ADDR = 32'h8000_0000 -) ( - input wire i_wb_clk, - 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 [NUM_BANKS*32-1:0] i_gpio, - output reg [31:0] o_wb_rdt, - output reg o_wb_ack, - output wire [NUM_BANKS*32-1:0] o_gpio + parameter num_banks = 4 +)( + input wire i_clk, + input wire i_rst, + + input wire [31:0] i_wb_adr, + input wire [31:0] i_wb_dat, + output reg [31:0] o_wb_rdt, + 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, + + input wire [num_banks*32-1:0] i_gpio, + output wire [num_banks*32-1:0] o_gpio ); + localparam sw = `CLOG2(num_banks); + wire [num_banks-1:0] bank_sel; - wire [NUM_BANKS-1:0] bank_sel; - wire [NUM_BANKS-1:0] bank_stb; - wire [NUM_BANKS*32-1:0] bank_rdt; - wire [NUM_BANKS-1:0] bank_ack; + wire [num_banks-1:0] bank_ack; + wire [num_banks*32-1:0] bank_rdt; - genvar gi; - generate - for (gi = 0; gi < NUM_BANKS; gi = gi + 1) begin : gen_gpio - localparam [31:0] BANK_ADDR = BASE_ADDR + (gi * 4); + genvar gi; + generate + for(gi=0; gi Date: Mon, 2 Mar 2026 18:02:47 +0100 Subject: [PATCH 11/12] Added new master checker and changed to synchronous reset checks --- .../formal/formal_wb_master_checker.v | 278 ++++++++++++------ .../formal/formal_wb_slave_checker.v | 23 +- .../formal/jtag_wb_bridge_prove/config.sby | 13 + .../formal/jtag_wb_bridge_prove/logfile.txt | 2 + .../formal/jtag_wb_bridge_prove/status.path | 1 + 5 files changed, 221 insertions(+), 96 deletions(-) create mode 100644 cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/config.sby create mode 100644 cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/logfile.txt create mode 100644 cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/status.path diff --git a/cores/wb/formal_checker/formal/formal_wb_master_checker.v b/cores/wb/formal_checker/formal/formal_wb_master_checker.v index 13f4ae9..86ad833 100644 --- a/cores/wb/formal_checker/formal/formal_wb_master_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_master_checker.v @@ -1,99 +1,205 @@ -`timescale 1ns/1ps +// formal_wb_master_checker.v +// +// Wishbone Classic master-side protocol checker (plain Verilog). +// Use when your DUT is a *master* and the bus/slave is the environment. -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 +module formal_wb_master_checker #( + parameter OPT_USE_ERR = 0, + parameter OPT_USE_RTY = 0, + + // If 1: require responses only when STB=1 (stricter than spec permission). + // If 0: allow "ghost" responses with STB=0; we only COUNT termination when STB=1. + parameter OPT_STRICT_RESP_WITH_STB = 0, + + // If 1: require ACK/ERR/RTY to be single-cycle pulses. + // If 0: allow them to be held. + parameter OPT_PULSE_RESP = 1, + + // If 1: after a synchronous reset has been asserted for one clock, + // require the master to hold CYC/STB low. + parameter OPT_STRICT_RESET = 1, + + // Optional widths + parameter AW = 32, + parameter DW = 32, + parameter SW = 4 +) ( + input wire i_clk, + input wire i_rst, + input wire i_wb_rst, + + // Master -> bus + input wire i_wb_cyc, + input wire i_wb_stb, + input wire i_wb_we, + input wire [AW-1:0] i_wb_adr, + input wire [DW-1:0] i_wb_dat, + input wire [SW-1:0] i_wb_sel, + + // Bus/slave -> master + input wire o_wb_ack, + input wire o_wb_err, + input wire o_wb_rty, + input wire [DW-1:0] o_wb_rdt ); + +`ifdef FORMAL + // ----------------------------- + // Reset combine + // ----------------------------- + wire rst_any; + assign rst_any = i_rst | i_wb_rst; + + // ----------------------------- + // Formal infrastructure + // ----------------------------- reg f_past_valid; - initial f_past_valid = 1'b0; - - always @(posedge i_clk) begin + always @(posedge i_clk) f_past_valid <= 1'b1; - // A1: Slave ACK must correspond to either a same-cycle or previous-cycle request - if(o_wb_ack) - A1: assume( - (i_wb_cyc && i_wb_stb) || - (f_past_valid && $past(i_wb_cyc && i_wb_stb)) - ); + wire f_resp_any; + assign f_resp_any = o_wb_ack + | (OPT_USE_ERR ? o_wb_err : 1'b0) + | (OPT_USE_RTY ? o_wb_rty : 1'b0); - // A2: Slave must not ACK outside an active cycle - if(!i_wb_cyc) - A2: assume(!o_wb_ack); + // A "real" termination in Classic requires STB high (handshake qualifies the transfer) + wire f_terminate; + assign f_terminate = i_wb_cyc && i_wb_stb && f_resp_any; - // 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 - ) - A3: assume(!o_wb_ack); + // ----------------------------- + // Track exactly one outstanding request (Classic) + // ----------------------------- + reg f_pending; + initial f_pending = 1'b0; - // R1: Reset must leave the master initialized on the following cycle - if(f_past_valid && $past(i_rst || i_wb_rst)) begin - R1: assert(!i_wb_cyc); - R2: assert(!i_wb_stb); + always @(posedge i_clk or posedge rst_any) begin + if (rst_any) begin + f_pending <= 1'b0; + end else if (!i_wb_cyc) begin + // Dropping CYC ends the bus cycle and clears any outstanding request + f_pending <= 1'b0; + end else begin + // Start pending when STB is asserted and none is pending + if (!f_pending && i_wb_stb) + f_pending <= 1'b1; + + // Clear pending only on a real termination (STB && response) + if (f_terminate) + f_pending <= 1'b0; end - - // R3: STB never high without CYC - if(i_wb_stb) - R3: assert(i_wb_cyc); - - // R4-R9: Once a request starts, hold it stable until the slave responds - if( - f_past_valid && - !$past(i_rst || i_wb_rst) && - $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && - !o_wb_ack && - !(i_rst || i_wb_rst) - ) begin - R4: assert(i_wb_cyc); - R5: assert(i_wb_stb); - R6: assert(i_wb_adr == $past(i_wb_adr)); - R7: assert(i_wb_dat == $past(i_wb_dat)); - R8: assert(i_wb_sel == $past(i_wb_sel)); - R9: assert(i_wb_we == $past(i_wb_we)); - end - - // R10: Once CYC is low, STB must also be low - if(!i_wb_cyc) - R10: assert(!i_wb_stb); - - // C1: We eventually initiate a request - C1: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb); - - // C2: We eventually get an ACK during an active request - C2: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && o_wb_ack); - - // C3: A delayed ACK occurs for a request issued in a previous cycle. - // This does not require the request to drop in the ACK cycle. - C3: cover(f_past_valid && !i_rst && !i_wb_rst && - $past(i_wb_cyc && i_wb_stb) && o_wb_ack); - - // C4: A “wait state” happens: request asserted, no ACK for at least 1 cycle - C4: cover(f_past_valid && !i_rst && !i_wb_rst && - $past(i_wb_cyc && i_wb_stb && !o_wb_ack) && - (i_wb_cyc && i_wb_stb && !o_wb_ack)); - - // C5-C6: Read and write both occur (even if only once each) - C5: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && !i_wb_we); - C6: cover(f_past_valid && !i_rst && !i_wb_rst && i_wb_cyc && i_wb_stb && i_wb_we); - - // C7: A transfer completes and the master drops CYC sometime after - C7: cover(f_past_valid && !i_rst && !i_wb_rst && - $past(i_wb_cyc && i_wb_stb && o_wb_ack) && !i_wb_cyc); - end - wire unused = &{1'b0, o_wb_rdt}; + // ----------------------------- + // Reset rules (Wishbone synchronous reset semantics) + // ----------------------------- + always @(posedge i_clk) if (f_past_valid) begin + if (rst_any) begin + // R00: Monitor pending state must be cleared during reset + R00: assert(f_pending == 1'b0); + + if (OPT_STRICT_RESET && $past(rst_any)) begin + // R01: After reset was asserted on the prior clock, master must be idle + R01: assert(!i_wb_cyc); + // R02: After reset was asserted on the prior clock, master must not strobe + R02: assert(!i_wb_stb); + end + + if ($past(rst_any)) begin + // A00: After reset was asserted on the prior clock, environment should not respond + A00: assume(!o_wb_ack); + if (OPT_USE_ERR) A01: assume(!o_wb_err); + if (OPT_USE_RTY) A02: assume(!o_wb_rty); + end + end + end + + // ----------------------------- + // Master-side RULES (ASSERTIONS) + // ----------------------------- + always @(posedge i_clk) if (f_past_valid && !rst_any) begin + // R10: STB must imply CYC (no strobe outside of a cycle) + if (i_wb_stb) begin + R10: assert(i_wb_cyc); + end + + // R11: While a request is pending and NOT terminated, master must keep CYC asserted + if ($past(f_pending) && !$past(f_terminate)) begin + R11: assert(i_wb_cyc); + end + + // R12: While a request is pending and NOT terminated, master must keep STB asserted + if ($past(f_pending) && !$past(f_terminate)) begin + R12: assert(i_wb_stb); + end + + // R13: Address stable while pending and not terminated + if ($past(f_pending) && !$past(f_terminate)) begin + R13: assert(i_wb_adr == $past(i_wb_adr)); + end + + // R14: WE stable while pending and not terminated + if ($past(f_pending) && !$past(f_terminate)) begin + R14: assert(i_wb_we == $past(i_wb_we)); + end + + // R15: SEL stable while pending and not terminated + if ($past(f_pending) && !$past(f_terminate)) begin + R15: assert(i_wb_sel == $past(i_wb_sel)); + end + + // R16: Write data stable while pending and not terminated + if ($past(f_pending) && !$past(f_terminate)) begin + R16: assert(i_wb_dat == $past(i_wb_dat)); + end + end + + // ----------------------------- + // Environment SLAVE assumptions + // ----------------------------- + always @(posedge i_clk) if (f_past_valid && !rst_any) begin + // A10: Any response must occur only during an active cycle + if (f_resp_any) begin + A10: assume(i_wb_cyc); + end + + // A11: Optional strict profile: response only when STB=1 + if (OPT_STRICT_RESP_WITH_STB && f_resp_any) begin + A11: assume(i_wb_stb); + end + + // A12-A14: Mutual exclusion between ACK/ERR/RTY (recommended) + if (o_wb_ack) begin + if (OPT_USE_ERR) A12s1: assume(!o_wb_err); + if (OPT_USE_RTY) A12s2: assume(!o_wb_rty); + end + if (OPT_USE_ERR && o_wb_err) begin + A13s1: assume(!o_wb_ack); + if (OPT_USE_RTY) A13s2: assume(!o_wb_rty); + end + if (OPT_USE_RTY && o_wb_rty) begin + A14s1: assume(!o_wb_ack); + if (OPT_USE_ERR) A14s2: assume(!o_wb_err); + end + + // A15-A17: Optional pulse-only responses + if (OPT_PULSE_RESP) begin + if ($past(o_wb_ack)) begin + A15: assume(!o_wb_ack); + end + if (OPT_USE_ERR && $past(o_wb_err)) begin + A16: assume(!o_wb_err); + end + if (OPT_USE_RTY && $past(o_wb_rty)) begin + A17: assume(!o_wb_rty); + end + end + end + + // ----------------------------- + // Coverage: explore protocol space ("state diagram" witnesses) + // ----------------------------- + // TODO + +`endif endmodule diff --git a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v index 479bdbc..b277482 100644 --- a/cores/wb/formal_checker/formal/formal_wb_slave_checker.v +++ b/cores/wb/formal_checker/formal/formal_wb_slave_checker.v @@ -15,7 +15,8 @@ module formal_wb_slave_checker #( // If 0: allow them to be held. parameter OPT_PULSE_RESP = 1, - // If 1: during reset require master to hold CYC/STB low (assumption; common convention). + // If 1: after a synchronous reset has been asserted for one clock, + // require the master to hold CYC/STB low. parameter OPT_STRICT_RESET = 1, // If 1: assert read data stable while ACK is held (useful if you allow ACK-hold). @@ -90,24 +91,26 @@ module formal_wb_slave_checker #( end // ----------------------------- - // Reset rules (recommended) + // Reset rules (Wishbone synchronous reset semantics) // ----------------------------- always @(posedge i_clk) if (f_past_valid) begin if (rst_any) begin // R00: Monitor pending state must be cleared during reset R00: assert(f_pending == 1'b0); - if (OPT_STRICT_RESET) begin - // A00: During reset, assume the master is not attempting a bus cycle + if (OPT_STRICT_RESET && $past(rst_any)) begin + // A00: After reset was asserted on the prior clock, assume master is idle A00: assume(!i_wb_cyc); - // A01: During reset, assume the master is not strobing + // A01: After reset was asserted on the prior clock, assume master is not strobing A01: assume(!i_wb_stb); end - // R01: Slave should not respond during reset (prevents silly traces) - R01: assert(!o_wb_ack); - if (OPT_USE_ERR) R02: assert(!o_wb_err); - if (OPT_USE_RTY) R03: assert(!o_wb_rty); + if ($past(rst_any)) begin + // R01: After reset was asserted on the prior clock, slave must not respond + R01: assert(!o_wb_ack); + if (OPT_USE_ERR) R02: assert(!o_wb_err); + if (OPT_USE_RTY) R03: assert(!o_wb_rty); + end end end @@ -216,4 +219,4 @@ module formal_wb_slave_checker #( // TODO `endif -endmodule \ No newline at end of file +endmodule diff --git a/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/config.sby b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/config.sby new file mode 100644 index 0000000..29b2876 --- /dev/null +++ b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/config.sby @@ -0,0 +1,13 @@ +[options] +mode prove + +[engines] +abc pdr + +[script] +{{"-formal"|gen_reads}} +prep -top {{top_level}} +clk2fflogic + +[files] +{{files}} diff --git a/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/logfile.txt b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/logfile.txt new file mode 100644 index 0000000..2c0f76b --- /dev/null +++ b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/logfile.txt @@ -0,0 +1,2 @@ +SBY 17:52:48 [/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove] Removing directory '/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove'. +SBY 17:52:48 [/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove] Copy '/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/{{files}}' to '/data/joppe/projects/fusesoc_test/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/src/{{files}}'. diff --git a/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/status.path b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/status.path new file mode 100644 index 0000000..9b60661 --- /dev/null +++ b/cores/wb/jtag_wb_bridge/formal/jtag_wb_bridge_prove/status.path @@ -0,0 +1 @@ +../jtag_wb_bridge/status.sqlite From 4e3521e94acc010ae805c4e96956f1101d6b47f4 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Mon, 2 Mar 2026 19:28:36 +0100 Subject: [PATCH 12/12] Added missing signal modules --- .../lvds_comparator/lvds_comparator.core | 26 ++++ .../lvds_comparator/lvds_comparator.v | 19 +++ .../lvds_comparator_generic_impl.v | 7 ++ .../lvds_comparator_spartan6.v | 14 +++ .../decimate_by_r_q15.core | 29 +++++ .../decimate_by_r_q15.v/decimate_by_r_q15.v | 74 ++++++++++++ cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.core | 24 ++++ cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.v | 64 ++++++++++ cores/signal/nco_q15/rtl/nco_q15.v | 108 ++++++++--------- cores/signal/nco_q15/tb/tb_nco_q15.v | 14 +-- cores/signal/sd_adc_q15/rtl/rc_alpha_q15.vh | 91 ++++++++++++++ cores/signal/sd_adc_q15/rtl/rcmodel_q15.v | 55 +++++++++ cores/signal/sd_adc_q15/rtl/sd_adc_q15.v | 57 +++++++++ cores/signal/sd_adc_q15/rtl/sd_sampler.v | 18 +++ cores/signal/sd_adc_q15/sd_adc_q15.core | 57 +++++++++ cores/signal/sd_adc_q15/sim/sd_sampler.v | 111 ++++++++++++++++++ cores/signal/sd_adc_q15/tb/tb_sd_adc_q15.v | 36 ++++++ cores/system/test/rtl/toplevel.v | 12 +- cores/util/mul_const/mul_const.core | 15 +++ cores/util/mul_const/rtl/mul_const.v | 31 +++++ 20 files changed, 795 insertions(+), 67 deletions(-) create mode 100644 cores/primitive/lvds_comparator/lvds_comparator.core create mode 100644 cores/primitive/lvds_comparator/lvds_comparator.v create mode 100644 cores/primitive/lvds_comparator/lvds_comparator_generic_impl.v create mode 100644 cores/primitive/lvds_comparator/lvds_comparator_spartan6.v create mode 100644 cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.core create mode 100644 cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.v create mode 100644 cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.core create mode 100644 cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.v create mode 100644 cores/signal/sd_adc_q15/rtl/rc_alpha_q15.vh create mode 100644 cores/signal/sd_adc_q15/rtl/rcmodel_q15.v create mode 100644 cores/signal/sd_adc_q15/rtl/sd_adc_q15.v create mode 100644 cores/signal/sd_adc_q15/rtl/sd_sampler.v create mode 100644 cores/signal/sd_adc_q15/sd_adc_q15.core create mode 100644 cores/signal/sd_adc_q15/sim/sd_sampler.v create mode 100644 cores/signal/sd_adc_q15/tb/tb_sd_adc_q15.v create mode 100644 cores/util/mul_const/mul_const.core create mode 100644 cores/util/mul_const/rtl/mul_const.v diff --git a/cores/primitive/lvds_comparator/lvds_comparator.core b/cores/primitive/lvds_comparator/lvds_comparator.core new file mode 100644 index 0000000..efb8098 --- /dev/null +++ b/cores/primitive/lvds_comparator/lvds_comparator.core @@ -0,0 +1,26 @@ +CAPI=2: + +name: joppeb:primitive:lvds_comparator:1.0 +description: LVDS comparator wrapper + +filesets: + wrapper: + files: + - lvds_comparator.v + file_type: verilogSource + generic: + files: + - lvds_comparator_generic_impl.v + file_type: verilogSource + spartan6: + files: + - lvds_comparator_spartan6.v + file_type: verilogSource + +targets: + default: + filesets: + - wrapper + - generic + - spartan6 + toplevel: lvds_comparator diff --git a/cores/primitive/lvds_comparator/lvds_comparator.v b/cores/primitive/lvds_comparator/lvds_comparator.v new file mode 100644 index 0000000..b3076e4 --- /dev/null +++ b/cores/primitive/lvds_comparator/lvds_comparator.v @@ -0,0 +1,19 @@ +module lvds_comparator( + input wire a, + input wire b, + output wire o +); +`ifdef FPGA_SPARTAN6 + lvds_comparator_spartan6_impl impl_i ( + .a(a), + .b(b), + .o(o) + ); +`else + lvds_comparator_generic_impl impl_i ( + .a(a), + .b(b), + .o(o) + ); +`endif +endmodule \ No newline at end of file diff --git a/cores/primitive/lvds_comparator/lvds_comparator_generic_impl.v b/cores/primitive/lvds_comparator/lvds_comparator_generic_impl.v new file mode 100644 index 0000000..2c495a2 --- /dev/null +++ b/cores/primitive/lvds_comparator/lvds_comparator_generic_impl.v @@ -0,0 +1,7 @@ +module lvds_comparator_generic_impl ( + input wire a, + input wire b, + output wire o +); + assign o = a; +endmodule \ No newline at end of file diff --git a/cores/primitive/lvds_comparator/lvds_comparator_spartan6.v b/cores/primitive/lvds_comparator/lvds_comparator_spartan6.v new file mode 100644 index 0000000..2eeaa04 --- /dev/null +++ b/cores/primitive/lvds_comparator/lvds_comparator_spartan6.v @@ -0,0 +1,14 @@ +module lvds_comparator_spartan6_impl ( + input wire a, + input wire b, + output wire o +); + IBUFDS #( + .DIFF_TERM("FALSE"), + .IOSTANDARD("LVDS33") + ) lvds_buf ( + .O(o), + .I(a), + .IB(b) + ); +endmodule \ No newline at end of file diff --git a/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.core b/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.core new file mode 100644 index 0000000..418144c --- /dev/null +++ b/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.core @@ -0,0 +1,29 @@ +CAPI=2: + +name: joppeb:signal:decimate_by_r_q15:1.0 +description: Q1.15 integer-rate decimator + +filesets: + rtl: + files: + - decimate_by_r_q15.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: decimate_by_r_q15 + parameters: + - R + - CNT_W + +parameters: + R: + datatype: int + description: Integer decimation ratio + paramtype: vlogparam + CNT_W: + datatype: int + description: Counter width for the decimation counter + paramtype: vlogparam diff --git a/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.v b/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.v new file mode 100644 index 0000000..49aedeb --- /dev/null +++ b/cores/signal/decimate_by_r_q15.v/decimate_by_r_q15.v @@ -0,0 +1,74 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Decimator by R +// Reduces the effective sample rate by an integer factor R by selecting every +// R-th input sample. Generates a one-cycle 'o_valid' pulse each time a new +// decimated sample is produced. +// +// Implements: +// For each valid input sample: +// if (count == R-1): +// count <= 0 +// o_q15 <= i_q15 +// o_valid <= 1 +// else: +// count <= count + 1 +// +// parameters: +// -- R : integer decimation factor (e.g., 400) +// output sample rate = input rate / R +// -- CNT_W : counter bit width, must satisfy 2^CNT_W > R +// +// inout: +// -- i_clk : input clock (same rate as 'i_valid') +// -- i_rst_n : active-low synchronous reset +// -- i_valid : input data strobe; assert 1'b1 if input is always valid +// -- i_q15 : signed 16-bit Q1.15 input sample (full-rate) +// -- o_valid : single-cycle pulse every R samples (decimated rate strobe) +// -- o_q15 : signed 16-bit Q1.15 output sample (decimated stream) +// +// Notes: +// - This module performs *pure downsampling* (sample selection only). +// It does not include any anti-alias filtering; high-frequency content +// above the new Nyquist limit (Fs_out / 2) will alias into the baseband. +// - For most applications, an anti-alias low-pass filter such as +// lpf_iir_q15 or a FIR stage should precede this decimator. +// - The output sample rate is given by: +// Fs_out = Fs_in / R +// - Typical usage: interface between high-rate sigma-delta or oversampled +// data streams and lower-rate processing stages. +// ============================================================================= +module decimate_by_r_q15 #( + parameter integer R = 400, // decimation factor + parameter integer CNT_W = 10 // width so that 2^CNT_W > R (e.g., 10 for 750) +)( + input wire i_clk, + input wire i_rst_n, + input wire i_valid, // assert 1'b1 if always valid + input wire signed [15:0] i_q15, // Q1.15 sample at full rate + output reg o_valid, // 1-cycle pulse every R samples + output reg signed [15:0] o_q15 // Q1.15 sample at decimated rate +); + reg [CNT_W-1:0] cnt; + + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) begin + cnt <= {CNT_W{1'b0}}; + o_valid <= 1'b0; + o_q15 <= 16'sd0; + end else begin + o_valid <= 1'b0; + + if (i_valid) begin + if (cnt == R-1) begin + cnt <= {CNT_W{1'b0}}; + o_q15 <= i_q15; + o_valid <= 1'b1; + end else begin + cnt <= cnt + 1'b1; + end + end + end + end +endmodule diff --git a/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.core b/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.core new file mode 100644 index 0000000..b134b5d --- /dev/null +++ b/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.core @@ -0,0 +1,24 @@ +CAPI=2: + +name: joppeb:signal:lpf_iir_q15_k:1.0 +description: First-order Q1.15 IIR low-pass filter + +filesets: + rtl: + files: + - lpf_iir_q15_k.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl + toplevel: lpf_iir_q15_k + parameters: + - K + +parameters: + K: + datatype: int + description: Filter shift factor + paramtype: vlogparam diff --git a/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.v b/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.v new file mode 100644 index 0000000..bfcaaa0 --- /dev/null +++ b/cores/signal/lpf_iir_q15_k/lpf_iir_q15_k.v @@ -0,0 +1,64 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Low-Pass IIR Filter (Q1.15) +// Simple first-order infinite impulse response filter, equivalent to an +// exponential moving average. Provides an adjustable smoothing factor based +// on parameter K. +// +// Implements: +// Y[n+1] = Y[n] + (X[n] - Y[n]) / 2^K +// +// This is a purely digital one-pole low-pass filter whose time constant +// approximates that of an analog RC filter, where alpha = 1 / 2^K. +// +// The larger K is, the slower the filter responds (stronger smoothing). +// The smaller K is, the faster it reacts to changes. +// +// parameters: +// -- K : filter shift factor (integer, 4..14 typical) +// cutoff frequency ≈ Fs / (2π * 2^K) +// larger K → lower cutoff +// +// inout: +// -- i_clk : input clock +// -- i_rst_n : active-low reset +// -- i_x_q15 : signed 16-bit Q1.15 input sample (e.g., 0..0x7FFF) +// -- o_y_q15 : signed 16-bit Q1.15 filtered output +// +// Notes: +// - The arithmetic right shift implements division by 2^K. +// - Internal arithmetic is Q1.15 fixed-point with saturation +// to [0, 0x7FFF] (for non-negative signals). +// - Useful for smoothing noisy ADC / sigma-delta data streams +// or modeling an RC envelope follower. +// ============================================================================= +module lpf_iir_q15_k #( + parameter integer K = 10 // try 8..12; bigger = more smoothing +)( + input wire i_clk, + input wire i_rst_n, + input wire signed [15:0] i_x_q15, // Q1.15 input (e.g., 0..0x7FFF) + output reg signed [15:0] o_y_q15 // Q1.15 output +); + wire signed [15:0] e_q15 = i_x_q15 - o_y_q15; + wire signed [15:0] delta_q15 = e_q15 >>> K; // arithmetic shift + wire signed [15:0] y_next = o_y_q15 + delta_q15; // clamp to [0, 0x7FFF] (handy if your signal is non-negative) + + function signed [15:0] clamp01; + input signed [15:0] v; + begin + if (v < 16'sd0) + clamp01 = 16'sd0; + else if (v > 16'sh7FFF) + clamp01 = 16'sh7FFF; + else + clamp01 = v; + end + endfunction + + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) o_y_q15 <= 16'sd0; + else o_y_q15 <= clamp01(y_next); + end +endmodule diff --git a/cores/signal/nco_q15/rtl/nco_q15.v b/cores/signal/nco_q15/rtl/nco_q15.v index fe20395..13c6bb4 100644 --- a/cores/signal/nco_q15/rtl/nco_q15.v +++ b/cores/signal/nco_q15/rtl/nco_q15.v @@ -8,23 +8,23 @@ // -- CLK_HZ : input clock frequency in Hz // -- FS_HZ : output sample frequency in Hz // inout: -// -- clk : input clock -// -- rst_n : reset -// -- freq_hz : decimal number of desired generated frequency in Hz, 0-FS/2 -// -- sin_q15/cos_q15 : I and Q outputs -// -- clk_en : output valid strobe +// -- i_clk : input clock +// -- i_rst_n : reset +// -- i_freq_hz : decimal number of desired generated frequency in Hz, 0-FS/2 +// -- o_sin_q15/o_cos_q15 : I and Q outputs +// -- o_clk_en : output valid strobe // ============================================================================= module nco_q15 #( parameter integer CLK_HZ = 120_000_000, // input clock parameter integer FS_HZ = 40_000 // sample rate )( - input wire clk, // CLK_HZ domain - input wire rst_n, // async active-low reset - input wire [31:0] freq_hz, // desired output frequency (Hz), 0..FS_HZ/2 + input wire i_clk, // CLK_HZ domain + input wire i_rst_n, // async active-low reset + input wire [31:0] i_freq_hz, // desired output frequency (Hz), 0..FS_HZ/2 - output reg signed [15:0] sin_q15, // Q1.15 sine - output reg signed [15:0] cos_q15, // Q1.15 cosine - output reg clk_en // 1-cycle strobe @ FS_HZ + output reg signed [15:0] o_sin_q15, // Q1.15 sine + output reg signed [15:0] o_cos_q15, // Q1.15 cosine + output reg o_clk_en // 1-cycle strobe @ FS_HZ ); localparam integer PHASE_FRAC_BITS = 6; localparam integer QTR_ADDR_BITS = 6; @@ -41,17 +41,17 @@ module nco_q15 #( endfunction reg [clog2(DIV)-1:0] tick_cnt; - always @(posedge clk or negedge rst_n) begin - if (!rst_n) begin tick_cnt <= 0; clk_en <= 1'b0; end + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) begin tick_cnt <= 0; o_clk_en <= 1'b0; end else begin - clk_en <= 1'b0; - if (tick_cnt == DIV-1) begin tick_cnt <= 0; clk_en <= 1'b1; end + o_clk_en <= 1'b0; + if (tick_cnt == DIV-1) begin tick_cnt <= 0; o_clk_en <= 1'b1; end else tick_cnt <= tick_cnt + 1'b1; end end - // 32-cycle shift-add multiply: prod = freq_hz * RECIP (no multiplications themself) - // Starts at clk_en, finishes in 32 cycles (<< available cycles per sample). + // 32-cycle shift-add multiply: prod = i_freq_hz * RECIP (no multiplications themself) + // Starts at o_clk_en, finishes in 32 cycles (<< available cycles per sample). reg mul_busy; reg [5:0] mul_i; // 0..31 reg [31:0] f_reg; @@ -59,15 +59,15 @@ module nco_q15 #( wire [95:0] recip_shift = {{32{1'b0}}, RECIP} << mul_i; // shift constant by i - always @(posedge clk or negedge rst_n) begin - if (!rst_n) begin + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) begin mul_busy <= 1'b0; mul_i <= 6'd0; f_reg <= 32'd0; acc <= 96'd0; end else begin - if (clk_en && !mul_busy) begin + if (o_clk_en && !mul_busy) begin // kick off a new multiply this sample mul_busy <= 1'b1; mul_i <= 6'd0; - f_reg <= (freq_hz > (FS_HZ>>1)) ? (FS_HZ>>1) : freq_hz; // clamp to Nyquist + f_reg <= (i_freq_hz > (FS_HZ>>1)) ? (FS_HZ>>1) : i_freq_hz; // clamp to Nyquist acc <= 96'd0; end else if (mul_busy) begin // add shifted RECIP if bit is set @@ -86,16 +86,16 @@ module nco_q15 #( wire [95:0] acc_round = acc + (96'd1 << (SHIFT-1)); wire [PHASE_BITS-1:0] ftw_next = acc_round[SHIFT +: PHASE_BITS]; // >> SHIFT - always @(posedge clk or negedge rst_n) begin - if (!rst_n) ftw_q <= {PHASE_BITS{1'b0}}; + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) ftw_q <= {PHASE_BITS{1'b0}}; else if (!mul_busy) ftw_q <= ftw_next; // update once product ready end // Phase accumulator (advance at FS_HZ) reg [PHASE_BITS-1:0] phase; - always @(posedge clk or negedge rst_n) begin - if (!rst_n) phase <= {PHASE_BITS{1'b0}}; - else if (clk_en) phase <= phase + ftw_q; + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) phase <= {PHASE_BITS{1'b0}}; + else if (o_clk_en) phase <= phase + ftw_q; end // Cosine phase = sine phase + 90° @@ -113,8 +113,8 @@ module nco_q15 #( // 64-entry quarter-wave LUT wire [7:0] mag_sin_u8, mag_cos_u8; - sine_qtr_lut64 u_lut_s (.addr(idx_sin), .dout(mag_sin_u8)); - sine_qtr_lut64 u_lut_c (.addr(idx_cos), .dout(mag_cos_u8)); + sine_qtr_lut64 u_lut_s (.i_addr(idx_sin), .o_dout(mag_sin_u8)); + sine_qtr_lut64 u_lut_c (.i_addr(idx_cos), .o_dout(mag_cos_u8)); // Scale to Q1.15 and apply sign wire signed [15:0] mag_sin_q15 = {1'b0, mag_sin_u8, 7'd0}; @@ -125,39 +125,39 @@ module nco_q15 #( wire signed [15:0] sin_next = sin_neg ? -mag_sin_q15 : mag_sin_q15; wire signed [15:0] cos_next = cos_neg ? -mag_cos_q15 : mag_cos_q15; - always @(posedge clk or negedge rst_n) begin - if (!rst_n) begin - sin_q15 <= 16'sd0; cos_q15 <= 16'sd0; - end else if (clk_en) begin - sin_q15 <= sin_next; cos_q15 <= cos_next; + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) begin + o_sin_q15 <= 16'sd0; o_cos_q15 <= 16'sd0; + end else if (o_clk_en) begin + o_sin_q15 <= sin_next; o_cos_q15 <= cos_next; end end endmodule module sine_qtr_lut64( - input wire [5:0] addr, - output reg [7:0] dout + input wire [5:0] i_addr, + output reg [7:0] o_dout ); always @* begin - case (addr) - 6'd0: dout = 8'd0; 6'd1: dout = 8'd6; 6'd2: dout = 8'd13; 6'd3: dout = 8'd19; - 6'd4: dout = 8'd25; 6'd5: dout = 8'd31; 6'd6: dout = 8'd37; 6'd7: dout = 8'd44; - 6'd8: dout = 8'd50; 6'd9: dout = 8'd56; 6'd10: dout = 8'd62; 6'd11: dout = 8'd68; - 6'd12: dout = 8'd74; 6'd13: dout = 8'd80; 6'd14: dout = 8'd86; 6'd15: dout = 8'd92; - 6'd16: dout = 8'd98; 6'd17: dout = 8'd103; 6'd18: dout = 8'd109; 6'd19: dout = 8'd115; - 6'd20: dout = 8'd120; 6'd21: dout = 8'd126; 6'd22: dout = 8'd131; 6'd23: dout = 8'd136; - 6'd24: dout = 8'd142; 6'd25: dout = 8'd147; 6'd26: dout = 8'd152; 6'd27: dout = 8'd157; - 6'd28: dout = 8'd162; 6'd29: dout = 8'd167; 6'd30: dout = 8'd171; 6'd31: dout = 8'd176; - 6'd32: dout = 8'd180; 6'd33: dout = 8'd185; 6'd34: dout = 8'd189; 6'd35: dout = 8'd193; - 6'd36: dout = 8'd197; 6'd37: dout = 8'd201; 6'd38: dout = 8'd205; 6'd39: dout = 8'd208; - 6'd40: dout = 8'd212; 6'd41: dout = 8'd215; 6'd42: dout = 8'd219; 6'd43: dout = 8'd222; - 6'd44: dout = 8'd225; 6'd45: dout = 8'd228; 6'd46: dout = 8'd231; 6'd47: dout = 8'd233; - 6'd48: dout = 8'd236; 6'd49: dout = 8'd238; 6'd50: dout = 8'd240; 6'd51: dout = 8'd242; - 6'd52: dout = 8'd244; 6'd53: dout = 8'd246; 6'd54: dout = 8'd247; 6'd55: dout = 8'd249; - 6'd56: dout = 8'd250; 6'd57: dout = 8'd251; 6'd58: dout = 8'd252; 6'd59: dout = 8'd253; - 6'd60: dout = 8'd254; 6'd61: dout = 8'd254; 6'd62: dout = 8'd255; 6'd63: dout = 8'd255; - default: dout=8'd0; + case (i_addr) + 6'd0: o_dout = 8'd0; 6'd1: o_dout = 8'd6; 6'd2: o_dout = 8'd13; 6'd3: o_dout = 8'd19; + 6'd4: o_dout = 8'd25; 6'd5: o_dout = 8'd31; 6'd6: o_dout = 8'd37; 6'd7: o_dout = 8'd44; + 6'd8: o_dout = 8'd50; 6'd9: o_dout = 8'd56; 6'd10: o_dout = 8'd62; 6'd11: o_dout = 8'd68; + 6'd12: o_dout = 8'd74; 6'd13: o_dout = 8'd80; 6'd14: o_dout = 8'd86; 6'd15: o_dout = 8'd92; + 6'd16: o_dout = 8'd98; 6'd17: o_dout = 8'd103; 6'd18: o_dout = 8'd109; 6'd19: o_dout = 8'd115; + 6'd20: o_dout = 8'd120; 6'd21: o_dout = 8'd126; 6'd22: o_dout = 8'd131; 6'd23: o_dout = 8'd136; + 6'd24: o_dout = 8'd142; 6'd25: o_dout = 8'd147; 6'd26: o_dout = 8'd152; 6'd27: o_dout = 8'd157; + 6'd28: o_dout = 8'd162; 6'd29: o_dout = 8'd167; 6'd30: o_dout = 8'd171; 6'd31: o_dout = 8'd176; + 6'd32: o_dout = 8'd180; 6'd33: o_dout = 8'd185; 6'd34: o_dout = 8'd189; 6'd35: o_dout = 8'd193; + 6'd36: o_dout = 8'd197; 6'd37: o_dout = 8'd201; 6'd38: o_dout = 8'd205; 6'd39: o_dout = 8'd208; + 6'd40: o_dout = 8'd212; 6'd41: o_dout = 8'd215; 6'd42: o_dout = 8'd219; 6'd43: o_dout = 8'd222; + 6'd44: o_dout = 8'd225; 6'd45: o_dout = 8'd228; 6'd46: o_dout = 8'd231; 6'd47: o_dout = 8'd233; + 6'd48: o_dout = 8'd236; 6'd49: o_dout = 8'd238; 6'd50: o_dout = 8'd240; 6'd51: o_dout = 8'd242; + 6'd52: o_dout = 8'd244; 6'd53: o_dout = 8'd246; 6'd54: o_dout = 8'd247; 6'd55: o_dout = 8'd249; + 6'd56: o_dout = 8'd250; 6'd57: o_dout = 8'd251; 6'd58: o_dout = 8'd252; 6'd59: o_dout = 8'd253; + 6'd60: o_dout = 8'd254; 6'd61: o_dout = 8'd254; 6'd62: o_dout = 8'd255; 6'd63: o_dout = 8'd255; + default: o_dout=8'd0; endcase end -endmodule \ No newline at end of file +endmodule diff --git a/cores/signal/nco_q15/tb/tb_nco_q15.v b/cores/signal/nco_q15/tb/tb_nco_q15.v index a85cb53..5f81fcc 100644 --- a/cores/signal/nco_q15/tb/tb_nco_q15.v +++ b/cores/signal/nco_q15/tb/tb_nco_q15.v @@ -15,12 +15,12 @@ module tb_nco_q15(); wire out_en; nco_q15 #(.CLK_HZ(120_000_000), .FS_HZ(40_000)) nco ( - .clk (clk), - .rst_n (resetn), - .freq_hz(freq), - .sin_q15(sin_q15), - .cos_q15(cos_q15), - .clk_en (out_en) + .i_clk (clk), + .i_rst_n (resetn), + .i_freq_hz(freq), + .o_sin_q15(sin_q15), + .o_cos_q15(cos_q15), + .o_clk_en (out_en) ); initial begin @@ -39,4 +39,4 @@ module tb_nco_q15(); $finish; end; -endmodule \ No newline at end of file +endmodule diff --git a/cores/signal/sd_adc_q15/rtl/rc_alpha_q15.vh b/cores/signal/sd_adc_q15/rtl/rc_alpha_q15.vh new file mode 100644 index 0000000..27e6804 --- /dev/null +++ b/cores/signal/sd_adc_q15/rtl/rc_alpha_q15.vh @@ -0,0 +1,91 @@ +// rc_alpha_q15.vh +// Plain Verilog-2001 constant function: R(ohm), C(pF), Fs(Hz) -> alpha_q15 (Q1.15) +// Uses fixed-point approximation: 1 - exp(-x) ≈ x - x^2/2 + x^3/6, where x = 1/(Fs*R*C) +// All integer math; suitable for elaboration-time constant folding (e.g., XST). + +`ifndef RC_ALPHA_Q15_VH +`define RC_ALPHA_Q15_VH + +function integer alpha_q15_from_rc; + input integer R_OHM; // ohms + input integer C_PF; // picofarads + input integer FS_HZ; // Hz + + // Choose QN for x. N=24 is a good balance for accuracy/width. + integer N; + + // We'll keep everything as unsigned vectors; inputs copied into vectors first. + reg [63:0] R_u, C_u, FS_u; + + // x = 1 / (Fs * R * C) with C in pF -> x = 1e12 / (Fs*R*C_pf) + // x_qN = round( x * 2^N ) = round( (1e12 << N) / denom ) + reg [127:0] NUM_1E12_SLLN; // big enough for 1e12 << N + reg [127:0] DENOM; // Fs*R*C + reg [127:0] X_qN; // x in QN + + // Powers + reg [255:0] X2; // x^2 in Q(2N) + reg [383:0] X3; // x^3 in Q(3N) + + integer term1_q15; + integer term2_q15; + integer term3_q15; + integer acc; + +begin + N = 24; + + // Copy integer inputs into 64-bit vectors (no bit-slicing of integers) + R_u = R_OHM[31:0]; + C_u = C_PF[31:0]; + FS_u = FS_HZ[31:0]; + + // Denominator = Fs * R * C_pf (fits in < 2^64 for typical values) + DENOM = 128'd0; + DENOM = FS_u; + DENOM = DENOM * R_u; + DENOM = DENOM * C_u; + + // // Guard: avoid divide by zero + // if (DENOM == 0) begin + // alpha_q15_from_rc = 0; + // disable alpha_q15_from_rc; + // end + + // Numerator = (1e12 << N). 1e12 * 2^24 ≈ 1.6777e19 (fits in 2^64..2^65), + // so use 128 bits to be safe. + NUM_1E12_SLLN = 128'd1000000000000 << N; + + // x_qN = rounded division + X_qN = (NUM_1E12_SLLN + (DENOM >> 1)) / DENOM; + + // Powers + X2 = X_qN * X_qN; + X3 = X2 * X_qN; + + // Convert terms to Q1.15: + // term1 = x -> shift from QN to Q15 + term1_q15 = (X_qN >> (N - 15)) & 16'hFFFF; + + // term2 = x^2 / 2 -> Q(2N) to Q15 and /2 + term2_q15 = (X2 >> (2*N - 15 + 1)) & 16'hFFFF; + + // term3 = x^3 / 6 -> Q(3N) to Q15, then /6 with rounding + begin : gen_t3 + reg [383:0] tmp_q15_wide; + reg [383:0] tmp_div6; + tmp_q15_wide = (X3 >> (3*N - 15)); + tmp_div6 = (tmp_q15_wide + 6'd3) / 6; + term3_q15 = tmp_div6[15:0]; + end + + // Combine and clamp + acc = term1_q15 - term2_q15 + term3_q15; + if (acc < 0) acc = 0; + else if (acc > 16'h7FFF) acc = 16'h7FFF; + + alpha_q15_from_rc = acc; +end +endfunction + +`endif diff --git a/cores/signal/sd_adc_q15/rtl/rcmodel_q15.v b/cores/signal/sd_adc_q15/rtl/rcmodel_q15.v new file mode 100644 index 0000000..505d4f5 --- /dev/null +++ b/cores/signal/sd_adc_q15/rtl/rcmodel_q15.v @@ -0,0 +1,55 @@ +`timescale 1ns/1ps + +// ============================================================================= +// RC model to convert sigma delta samples to Q1.15 +// Models the RC circuit on the outside of the FPGA +// Uses: Yn+1 = Yn + (sd - Yn)*(1-exp(-T/RC)) +// parameters: +// -- alpha_q15 : the 1-exp(-T/RC), defaults to R=3k3, C=220p and T=1/15MHz +// rounded to only use two bits (0b3b -> 0b00), the less +// bits the better +// inout: +// -- i_clk : input clock +// -- i_rst_n : reset signal +// -- i_sd_sample : 1 bit sample output from sd sampler +// -- o_sample_q15 : output samples in q.15 +// ============================================================================= +module rcmodel_q15 #( + parameter integer alpha_q15 = 16'sh0b00 +)( + input wire i_clk, + input wire i_rst_n, + input wire i_sd_sample, + output wire [15:0] o_sample_q15 +); + reg signed [15:0] y_q15; + wire signed [15:0] sd_q15 = i_sd_sample ? 16'sh7fff : 16'sh0000; + wire signed [15:0] e_q15 = sd_q15 - y_q15; + // wire signed [31:0] prod_q30 = $signed(e_q15) * $signed(alpha_q15); + wire signed [31:0] prod_q30; + // Use shift-add algorithm for multiplication + mul_const_shiftadd #( + .C($signed(alpha_q15)), + .IN_W(16), + .OUT_W(32) + ) alpha_times_e ( + .i_x(e_q15), + .o_y(prod_q30) + ); + wire signed [15:0] y_next_q15 = y_q15 + (prod_q30>>>15); + + // clamp to [0, 0x7FFF] (keeps signal view tidy) + function signed [15:0] clamp01_q15(input signed [15:0] v); + if (v < 16'sd0000) clamp01_q15 = 16'sd0000; + else if (v > 16'sh7FFF) clamp01_q15 = 16'sh7FFF; + else clamp01_q15 = v; + endfunction + + always @(posedge i_clk or negedge i_rst_n) begin + if (!i_rst_n) y_q15 <= 16'sd0000; + else y_q15 <= clamp01_q15(y_next_q15); + end + + assign o_sample_q15 = y_q15; + +endmodule diff --git a/cores/signal/sd_adc_q15/rtl/sd_adc_q15.v b/cores/signal/sd_adc_q15/rtl/sd_adc_q15.v new file mode 100644 index 0000000..7130a25 --- /dev/null +++ b/cores/signal/sd_adc_q15/rtl/sd_adc_q15.v @@ -0,0 +1,57 @@ +module sd_adc_q15 #( + parameter integer R_OHM = 3300, + parameter integer C_PF = 220 +)( + input wire i_clk_15, + input wire i_rst_n, + + input wire i_adc_a, + input wire i_adc_b, + output wire o_adc, + + output wire signed [15:0] o_signal_q15, + output wire o_signal_valid +); + `include "rc_alpha_q15.vh" + + wire sd_signal; + wire signed [15:0] raw_sample_biased; + wire signed [15:0] raw_sample_q15; + wire signed [15:0] lpf_sample_q15; + + sd_sampler sd_sampler( + .i_clk(i_clk_15), + .i_a(i_adc_a), .i_b(i_adc_b), + .o_sample(sd_signal) + ); + assign o_adc = sd_signal; + + localparam integer alpha_q15_int = alpha_q15_from_rc(R_OHM, C_PF, 15000000); + localparam signed [15:0] alpha_q15 = alpha_q15_int[15:0]; + localparam signed [15:0] alpha_q15_top = alpha_q15 & 16'hff00; + rcmodel_q15 #( + .alpha_q15(alpha_q15_top) + ) rc_model ( + .i_clk(i_clk_15), .i_rst_n(i_rst_n), + .i_sd_sample(sd_signal), + .o_sample_q15(raw_sample_q15) + ); + + lpf_iir_q15_k #( + .K(10) + ) lpf ( + .i_clk(i_clk_15), .i_rst_n(i_rst_n), + .i_x_q15(raw_sample_q15), + .o_y_q15(lpf_sample_q15) + ); + + decimate_by_r_q15 #( + .R(375), // 15MHz/375 = 40KHz + .CNT_W(10) + ) decimate ( + .i_clk(i_clk_15), .i_rst_n(i_rst_n), + .i_valid(1'b1), .i_q15(lpf_sample_q15), + .o_valid(o_signal_valid), .o_q15(o_signal_q15) + ); + +endmodule diff --git a/cores/signal/sd_adc_q15/rtl/sd_sampler.v b/cores/signal/sd_adc_q15/rtl/sd_sampler.v new file mode 100644 index 0000000..e17fa19 --- /dev/null +++ b/cores/signal/sd_adc_q15/rtl/sd_sampler.v @@ -0,0 +1,18 @@ +module sd_sampler( + input wire i_clk, + input wire i_a, + input wire i_b, + output wire o_sample +); + + wire comp_out; + lvds_comparator comp ( + .a(i_a), .b(i_b), .o(comp_out) + ); + + reg registered_comp_out; + always @(posedge i_clk) + registered_comp_out <= comp_out; + assign o_sample = registered_comp_out; + +endmodule diff --git a/cores/signal/sd_adc_q15/sd_adc_q15.core b/cores/signal/sd_adc_q15/sd_adc_q15.core new file mode 100644 index 0000000..361973d --- /dev/null +++ b/cores/signal/sd_adc_q15/sd_adc_q15.core @@ -0,0 +1,57 @@ +CAPI=2: + +name: joppeb:signal:sd_adc_q15:1.0 +description: Sigma-delta ADC front-end with Q1.15 output + +filesets: + rtl_common: + depend: + - joppeb:primitive:lvds_comparator + - joppeb:signal:lpf_iir_q15_k + - joppeb:signal:decimate_by_r_q15 + - joppeb:util:mul_const + files: + - rtl/rc_alpha_q15.vh: + is_include_file: true + - rtl/rcmodel_q15.v + - rtl/sd_adc_q15.v + file_type: verilogSource + rtl_sampler: + files: + - rtl/sd_sampler.v + file_type: verilogSource + sim_sampler: + files: + - sim/sd_sampler.v + file_type: verilogSource + tb: + files: + - tb/tb_sd_adc_q15.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl_common + - rtl_sampler + toplevel: sd_adc_q15 + parameters: + - R_OHM + - C_PF + sim: + default_tool: icarus + filesets: + - rtl_common + - sim_sampler + - tb + toplevel: tb_sd_adc_q15 + +parameters: + R_OHM: + datatype: int + description: RC filter resistor value in ohms + paramtype: vlogparam + C_PF: + datatype: int + description: RC filter capacitor value in pF + paramtype: vlogparam diff --git a/cores/signal/sd_adc_q15/sim/sd_sampler.v b/cores/signal/sd_adc_q15/sim/sd_sampler.v new file mode 100644 index 0000000..99f2d1a --- /dev/null +++ b/cores/signal/sd_adc_q15/sim/sd_sampler.v @@ -0,0 +1,111 @@ +`timescale 1ns/1ps + +// ============================================================================= +// Sigma-Delta sampler +// Simulates an RC circuit between o_sample and i_b and i_a sine at i_a +// ============================================================================= +module sd_sampler( + input wire i_clk, + input wire i_a, + input wire i_b, + output wire o_sample +); + + // Sine source (i_a input / P) + parameter real F_HZ = 5000; // input sine frequency (1 kHz) + parameter real AMP = 1.5; // sine amplitude (V) + parameter real VCM = 1.65; // common-mode (V), centered in 0..3.3V + + // Comparator behavior + parameter real VTH = 0.0; // threshold on (vp - vn) + parameter real VHYST = 0.05; // symmetric hysteresis half-width (V) + parameter integer ADD_HYST = 0; // 1 to enable hysteresis + + // 1-bit DAC rails (feedback into RC) + parameter real VLOW = 0.0; // DAC 0 (V) + parameter real VHIGH = 3.3; // DAC 1 (V) + + // RC filter (i_b input / N) + parameter real R_OHMS = 3300.0; // 3.3k + parameter real C_FARADS = 220e-12; // 220 pF + + // Integration step (ties to `timescale`) + parameter integer TSTEP_NS = 10; // sim step in ns (choose << tau) + + // ===== Internal state (simulation only) ===== + real vp, vn; // comparator i_a/i_b inputs + real v_rc; // RC node voltage (== vn) + real v_dac; // DAC output voltage from o_sample + real t_s; // time in seconds + real dt_s; // step in seconds + real tau_s; // R*C time constant in seconds + real two_pi; + reg q; // comparator latched output (pre-delay) + reg out; + reg sampler; + + initial sampler <= 1'b0; + always @(posedge i_clk) begin + sampler <= out; + end + assign o_sample = sampler; + + + // Helper task: update comparator with optional hysteresis + task automatic comp_update; + real diff; + begin + diff = (vp - vn); + + if (ADD_HYST != 0) begin + // simple symmetric hysteresis around VTH + if (q && (diff < (VTH - VHYST))) q = 1'b0; + else if (!q && (diff > (VTH + VHYST))) q = 1'b1; + // else hold + end else begin + q = (diff > VTH) ? 1'b1 : 1'b0; + end + end + endtask + + initial begin + // Init constants + two_pi = 6.283185307179586; + tau_s = R_OHMS * C_FARADS; // ~7.26e-7 s + dt_s = TSTEP_NS * 1.0e-9; + + // Init states + t_s = 0.0; + q = 1'b0; // start low + out = 1'b0; + v_dac= VLOW; + v_rc = (VHIGH + VLOW)/2.0; // start mid-rail to reduce start-up transient + vn = v_rc; + vp = VCM; + + // Main sim loop + forever begin + #(TSTEP_NS); // advance discrete time step + t_s = t_s + dt_s; + + // 1) Update DAC from previous comparator state + v_dac = sampler ? VHIGH : VLOW; + + // 2) RC low-pass driven by DAC: Euler step + // dv = (v_dac - v_rc) * dt/tau + v_rc = v_rc + (v_dac - v_rc) * (dt_s / tau_s); + vn = v_rc; + + // 3) Input sine on i_a + vp = VCM + AMP * $sin(two_pi * F_HZ * t_s); + + // 4) Comparator decision (with optional hysteresis) + comp_update(); + + // 5) Output with propagation delay + out = q; + end + end + + +endmodule diff --git a/cores/signal/sd_adc_q15/tb/tb_sd_adc_q15.v b/cores/signal/sd_adc_q15/tb/tb_sd_adc_q15.v new file mode 100644 index 0000000..c5ce51f --- /dev/null +++ b/cores/signal/sd_adc_q15/tb/tb_sd_adc_q15.v @@ -0,0 +1,36 @@ +`timescale 1ns/1ps + +module tb_sd_adc_q15(); + // Clock and reset generation + reg clk; + reg resetn; + initial clk <= 1'b0; + initial resetn <= 1'b0; + always #6.667 clk <= !clk; + initial #40 resetn <= 1'b1; + + // Default run + initial begin + $dumpfile("out.vcd"); + $dumpvars; + #2_000_000 + $finish; + end; + + wire sd_a; + wire sd_b; + wire sd_o; + wire signed [15:0] decimated_q15; + wire decimated_valid; + + sd_adc_q15 #( + .R_OHM(3300), + .C_PF(220) + ) dut( + .i_clk_15(clk), .i_rst_n(resetn), + .i_adc_a(sd_a), .i_adc_b(sd_b), .o_adc(sd_o), + .o_signal_q15(decimated_q15), + .o_signal_valid(decimated_valid) + ); + +endmodule diff --git a/cores/system/test/rtl/toplevel.v b/cores/system/test/rtl/toplevel.v index e14c9f8..49830a5 100644 --- a/cores/system/test/rtl/toplevel.v +++ b/cores/system/test/rtl/toplevel.v @@ -81,12 +81,12 @@ module toplevel #( .CLK_HZ(15_000_000), .FS_HZ(80_000) ) nco ( - .clk (clk_15), - .rst_n (sys_resetn), - .freq_hz(GPIO_A), - .sin_q15(sin_q15), - .cos_q15(), - .clk_en (clk_en) + .i_clk (clk_15), + .i_rst_n (sys_resetn), + .i_freq_hz(GPIO_A), + .o_sin_q15(sin_q15), + .o_cos_q15(), + .o_clk_en (clk_en) ); reg [5:0] dac_code; diff --git a/cores/util/mul_const/mul_const.core b/cores/util/mul_const/mul_const.core new file mode 100644 index 0000000..4602beb --- /dev/null +++ b/cores/util/mul_const/mul_const.core @@ -0,0 +1,15 @@ +CAPI=2: + +name: joppeb:util:mul_const:1.0 +description: Constant multiplier helpers implemented with shift-add logic + +filesets: + rtl: + files: + - rtl/mul_const.v + file_type: verilogSource + +targets: + default: + filesets: + - rtl diff --git a/cores/util/mul_const/rtl/mul_const.v b/cores/util/mul_const/rtl/mul_const.v new file mode 100644 index 0000000..481e9b0 --- /dev/null +++ b/cores/util/mul_const/rtl/mul_const.v @@ -0,0 +1,31 @@ +`timescale 1ns/1ps + +module mul_const_shiftadd #( + parameter integer C = 0, + parameter integer IN_W = 16, + parameter integer OUT_W = 32 +)( + input wire signed [IN_W-1:0] i_x, + output reg signed [OUT_W-1:0] o_y +); + integer k; + integer abs_c; + reg signed [OUT_W-1:0] acc; + reg signed [OUT_W-1:0] x_ext; + + always @* begin + abs_c = (C < 0) ? -C : C; + acc = {OUT_W{1'b0}}; + x_ext = {{(OUT_W-IN_W){i_x[IN_W-1]}}, i_x}; + + for (k = 0; k < 32; k = k + 1) begin + if (abs_c[k]) + acc = acc + (x_ext <<< k); + end + + if (C < 0) + o_y = -acc; + else + o_y = acc; + end +endmodule