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