使用SpinalHDL编写7段数码管模块,并使用cocotb进行仿真验证 实验环境
scala: 2.11.12
sbt script version: 1.10.5
Vivado 2023.2
cocotb 1.9.0
数码管扫描程序Verilog描述 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 module scan_led_hex_disp_4( input clk, rst, input [3 :0 ] hex0, hex1, hex2, hex3, input [3 :0 ] dp, output reg [3 :0 ] an, output reg [7 :0 ] sseg ); localparam N = 16 + 2 ; reg [N-1 :0 ] regN; always @(posedge clk, posedge rst) begin if (rst) regN <= 0 ; else regN <= regN + 1 ; end always @(*) begin case (regN[N-1 :N-2 ]) 2'b00 : begin an <= 4'b0001 ; sseg[6 :0 ] <= dt_translate(hex0); sseg[7 ] <= dp[3 ]; end 2'b01 : begin an <= 4'b0010 ; sseg[6 :0 ] <= dt_translate(hex1); sseg[7 ] <= dp[2 ]; end 2'b10 : begin an <= 4'b0100 ; sseg[6 :0 ] <= dt_translate(hex2); sseg[7 ] <= dp[1 ]; end 2'b11 : begin an <= 4'b1000 ; sseg[6 :0 ] <= dt_translate(hex3); sseg[7 ] <= dp[0 ]; end endcase end function [6 :0 ] dt_translate; input [3 :0 ] data; begin case (data) 4'd0 : dt_translate = 7'b1111110 ; 4'd1 : dt_translate = 7'b0110000 ; 4'd2 : dt_translate = 7'b1101101 ; 4'd3 : dt_translate = 7'b1111001 ; 4'd4 : dt_translate = 7'b0110011 ; 4'd5 : dt_translate = 7'b1011011 ; 4'd6 : dt_translate = 7'b1011111 ; 4'd7 : dt_translate = 7'b1110000 ; 4'd8 : dt_translate = 7'b1111111 ; 4'd9 : dt_translate = 7'b1111011 ; endcase end endfunction endmodule
使用SpinalHDL描述 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 class seven_seg_display extends Component { val io = new Bundle { val clk, rstn: Bool = in Bool () val hex0, hex1, hex2, hex3: Bits = in Bits (4 bits) val dp: Bits = in Bits (4 bits) val anodes: Bits = out Bits (4 bits) val segments: Bits = out Bits (8 bits) } val N_DIV = 16 + 2 val coreClockDomain = ClockDomain ( clock = io.clk, reset = io.rstn, config = ClockDomainConfig ( clockEdge = RISING , resetKind = ASYNC , resetActiveLevel = LOW ) ) val myCounter: UInt = Reg (UInt (N_DIV bits)) init (0 ) val counterArea = new ClockingArea (coreClockDomain) { myCounter := myCounter + 1 } val time_slot: UInt = myCounter(myCounter.high-1 , 2 bits) switch(time_slot) { is(0 ) { io.anodes := B "0001" io.segments := io.dp(0 ) ## hexToSegments(io.hex0) } is(1 ) { io.anodes := B "0010" io.segments := io.dp(1 ) ## hexToSegments(io.hex1) } is(2 ) { io.anodes := B "0100" io.segments := io.dp(2 ) ## hexToSegments(io.hex2) } is(3 ) { io.anodes := B "1000" io.segments := io.dp(3 ) ## hexToSegments(io.hex3) } } def hexToSegments (hex: Bits ): Bits = { val segments = Bits (7 bits) switch(hex) { is(B "4'x0" ) { segments := B "1111110" } is(B "4'x1" ) { segments := B "0110000" } is(B "4'x2" ) { segments := B "1101101" } is(B "4'x3" ) { segments := B "1111001" } is(B "4'x4" ) { segments := B "0110011" } is(B "4'x5" ) { segments := B "1011011" } is(B "4'x6" ) { segments := B "1011111" } is(B "4'x7" ) { segments := B "1110000" } is(B "4'x8" ) { segments := B "1111111" } is(B "4'x9" ) { segments := B "1111011" } is(B "4'xA" ) { segments := B "1110111" } is(B "4'xB" ) { segments := B "0011111" } is(B "4'xC" ) { segments := B "1001110" } is(B "4'xD" ) { segments := B "0111101" } is(B "4'xE" ) { segments := B "1001111" } is(B "4'xF" ) { segments := B "1000111" } } segments } }
SpinalHDL语法分析 1 2 3 4 5 6 7 val io = new Bundle { val clk, rstn: Bool = in Bool () val hex0, hex1, hex2, hex3: Bits = in Bits (4 bits) val dp: Bits = in Bits (4 bits) val anodes: Bits = out Bits (4 bits) val segments: Bits = out Bits (8 bits) }
Bool类型,可以视作一个bit的值,可以通过Bool()创建。
Bits类型,没有算数意义的bit向量,可以通过Bits(x bits)创建。
UInt/Sint类型,可以参与运算的符号数的bit向量。
Bundle,定义一组命名的信号,类似结构体,表示数据结构、总线或接口的模型。
时序逻辑分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 val coreClockDomain = ClockDomain ( clock = io.clk, reset = io.rstn, config = ClockDomainConfig ( clockEdge = RISING , resetKind = ASYNC , resetActiveLevel = LOW ) ) val time_slot = UInt (2 bits)val counterArea = new ClockingArea (coreClockDomain) { val myCounter: UInt = Reg (UInt (N_DIV bits)) init (0 ) myCounter := myCounter + 1 time_slot := myCounter(myCounter.high-1 , 2 bits) }
在Verilog中时序逻辑的主要载体为带有触发选项的always语句块,在SpinalHDL将时钟和复位信号(可选)结合定义为一个时钟域 ,通过ClockDomain
创建一个时钟域,通过ClockingArea
使用对应的时钟域。
生成的verilog语句如下:
1 2 3 4 5 6 7 always @(posedge io_clk or negedge io_rstn) begin if (!io_rstn) begin counterArea_myCounter <= 18'h0 ; end else begin counterArea_myCounter <= (counterArea_myCounter + 18'h00001 ); end end
仿真验证 仿真程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 """ @Project :cocotb_study @File :testbench_7segDisplay.py @Author :PLMaple @Date :2024/11/27 10:53 @Brief : 4位七段数码管模块仿真程序 """ import cocotbfrom cocotb.clock import Clockfrom cocotb.triggers import Timer, FallingEdge@cocotb.test() async def main (dut ): dut.io_hex0.value = 0x1 dut.io_hex1.value = 0x2 dut.io_hex2.value = 0x3 dut.io_hex3.value = 0x4 dut.io_dp.value = 0b1111 dut.io_rstn.value = 0 await Timer(20 , units='ns' ) dut.io_rstn.value = 1 clk_1mhz = Clock(dut.io_clk, 10 , 'ns' ) await cocotb.start(clk_1mhz.start()) local_capture = dut.io_anodes.value dut._log.info("初始化成功,当前片选为%s, 段选信号为%s" , dut.io_anodes.value.binstr, dut.io_segments.value.binstr) for i in range (4 ): while 1 : await FallingEdge(dut.io_clk) if dut.io_anodes.value != local_capture: local_capture = dut.io_anodes.value dut._log.info("片选信号改变,当前片选为%s, 段选信号为%s" , dut.io_anodes.value.binstr, dut.io_segments.value.binstr) break
仿真结果 1 2 3 4 5 6 7 0.00ns INFO cocotb.regression running main (1/1) 20.00ns INFO cocotb.seven_seg_display 初始化成功,当前片选为0001, 段选信号为10110000 655375.00ns INFO cocotb.seven_seg_display 片选信号改变,当前片选为0010, 段选信号为11101101 1310735.00ns INFO cocotb.seven_seg_display 片选信号改变,当前片选为0100, 段选信号为11111001 1966095.00ns INFO cocotb.seven_seg_display 片选信号改变,当前片选为1000, 段选信号为10110011 2621455.00ns INFO cocotb.seven_seg_display 片选信号改变,当前片选为0001, 段选信号为10110000 2621455.00ns INFO cocotb.regression main passed
片选信号的扫描周期为输入信号的65536倍分频 $$f = \frac{f_{in}}{2^{16}} $$
$$T = 65536*10(ns) = 0.65536(ms)$$
在20ns时,模块完成复位,此时时钟处于上升沿,由于测试程序的触发为下降沿,在$20 + 655360 - 5 = 655375(ns) $的下降沿处检测到片选信号更新,之后再次更新的周期即为$1310735 - 655375 = 65536*10(ns) $,测试结果无误。
参考资料
SpinalHDL’s documentation
cocotb’s documentation