小的时序电路设计
考虑下面的增量程序块的MyHDL代码
from myhdl import block, always_seq
@block
def inc(count, enable, clock, reset):
""" Incrementer with enable.
count -- output
enable -- control input, increment when 1
clock -- clock input
reset -- asynchronous reset input
"""
@always_seq(clock.posedge, reset=reset)
def seq():
if enable:
count.next = count + 1
return seq
该设计可以转换成Verilog和VHDL语言。第一步是精细化elaborate,就像我们为仿真所做的那样。然后,我们可以在详细的实例上使用转换方法。
from myhdl import Signal, ResetSignal, modbv
from inc import inc
def convert_inc(hdl):
"""Convert inc block to Verilog or VHDL."""
m = 8
count = Signal(modbv(0)[m:])
enable = Signal(bool(0))
clock = Signal(bool(0))
reset = ResetSignal(0, active=0, async=True)
inc_1 = inc(count, enable, clock, reset)
inc_1.convert(hdl=hdl)
convert_inc(hdl='Verilog')
convert_inc(hdl='VHDL')
为了灵活起见,我们将转换包装在CONVERT_INC函数中。INC1是一个详细的设计实例,提供了转换方法。
转换为Verilog将在文件inc.v中生成一个等效的Verilog模块。
Verilog代码如下所示:
// File: inc.v
// Generated by MyHDL 1.0dev
// Date: Sun May 22 18:46:37 2016
`timescale 1ns/10ps
module inc (
count,
enable,
clock,
reset
);
// Incrementer with enable.
//
// count -- output
// enable -- control input, increment when 1
// clock -- clock input
// reset -- asynchronous reset input
output [7:0] count;
reg [7:0] count;
input enable;
input clock;
input reset;
always @(posedge clock, negedge reset) begin: INC_SEQ
if (reset == 0) begin
count <= 0;
end
else begin
if (enable) begin
count <= (count + 1);
end
end
end
endmodule
转换器推导出合适的Verilog模块接口,并将MyHDL生成器映射到Verilog Always块。类似地,转换为VHDL会生成包含以下内容的文件inc.vhd:
-- File: inc.vhd
-- Generated by MyHDL 1.0dev
-- Date: Sun May 22 18:46:37 2016
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use std.textio.all;
use work.pck_myhdl_10.all;
entity inc is
port (
count: inout unsigned(7 downto 0);
enable: in std_logic;
clock: in std_logic;
reset: in std_logic
);
end entity inc;
-- Incrementer with enable.
--
-- count -- output
-- enable -- control input, increment when 1
-- clock -- clock input
-- reset -- asynchronous reset input
architecture MyHDL of inc is
begin
INC_SEQ: process (clock, reset) is
begin
if (reset = '0') then
count <= to_unsigned(0, 8);
elsif rising_edge(clock) then
if bool(enable) then
count <= (count + 1);
end if;
end if;
end process INC_SEQ;
end architecture MyHDL;
在这种情况下,MyHDL生成器被映射到VHDL进程。请注意,VHDL文件引用了名为pck_myhdl_的VHDL包。此程序包含许多方便的函数,这些函数使转换变得更容易。还请注意在接口中使用InOut。这不是建议的VHDL设计实践,但在这里需要有一个有效的VHDL设计,以匹配MyHDL设计的行为。由于这只是端口的问题,而且转换器输出是非分层的,因此这个问题不是很常见,并且有一个简单的解决方法。
小的组合电路设计
第二个例子是一个小的组合设计,更具体地说,是前面章节中的二进制到格雷码转换器:
from myhdl import block, always_comb
@block
def bin2gray(B, G):
""" Gray encoder.
B -- binary input
G -- Gray encoded output
"""
@always_comb
def logic():
G.next = (B>>1) ^ B
return logic
如前所述,可以创建实例并转换为Verilog和VHDL,如下所示:
from myhdl import Signal, intbv
from bin2gray import bin2gray
def convert(hdl, width=8):
B = Signal(intbv(0)[width:])
G = Signal(intbv(0)[width:])
inst = bin2gray(B, G)
inst.convert(hdl=hdl)
convert(hdl='Verilog')
convert(hdl='VHDL')
生成的Verilog代码如下所示:
// File: bin2gray.v
// Generated by MyHDL 1.0dev
// Date: Mon May 23 16:09:27 2016
`timescale 1ns/10ps
module bin2gray (
B,
G
);
// Gray encoder.
//
// B -- binary input
// G -- Gray encoded output
input [7:0] B;
output [7:0] G;
wire [7:0] G;
assign G = ((B >>> 1) ^ B);
endmodule
生成的VHDL代码如下所示:
-- File: bin2gray.vhd
-- Generated by MyHDL 1.0dev
-- Date: Mon May 23 16:09:27 2016
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use std.textio.all;
use work.pck_myhdl_10.all;
entity bin2gray is
port (
B: in unsigned(7 downto 0);
G: out unsigned(7 downto 0)
);
end entity bin2gray;
-- Gray encoder.
--
-- B -- binary input
-- G -- Gray encoded output
architecture MyHDL of bin2gray is
begin
G <= (shift_right(B, 1) xor B);
end architecture MyHDL;
分层设计
转换器可以处理任意深层次的设计。
例如,假设我们要设计一个具有格雷码输出的增量器。
使用前面几节中的设计,我们可以继续执行以下操作:
from myhdl import block, Signal, modbv
from bin2gray import bin2gray
from inc import inc
@block
def gray_inc(graycnt, enable, clock, reset, width):
bincnt = Signal(modbv(0)[width:])
inc_0 = inc(bincnt, enable, clock, reset)
bin2gray_0 = bin2gray(B=bincnt, G=graycnt)
return inc_0, bin2gray_0
根据格雷码的特性,在连续的值中只有一位会改变。然而,由于bin2Gray模块是组合的,所以输出比特可能会有短暂的毛刺,这可能是不可取的。
为了解决这个问题,让我们创建一个额外的层次结构,并向设计中添加一个输出寄存器。
(这将造成时钟周期的额外延迟,这可能是不可接受的,但我们将在此处忽略这一点。)
from myhdl import block, Signal, modbv
from bin2gray import bin2gray
from inc import inc
@block
def gray_inc(graycnt, enable, clock, reset, width):
bincnt = Signal(modbv(0)[width:])
inc_0 = inc(bincnt, enable, clock, reset)
bin2gray_0 = bin2gray(B=bincnt, G=graycnt)
return inc_0, bin2gray_0
我们可以按如下方式转换此分层设计:
from myhdl import Signal, ResetSignal, modbv
from gray_inc_reg import gray_inc_reg
def convert_gray_inc_reg(hdl, width=8):
graycnt = Signal(modbv(0)[width:])
enable = Signal(bool())
clock = Signal(bool())
reset = ResetSignal(0, active=0, async=True)
inst = gray_inc_reg(graycnt, enable, clock, reset, width)
inst.convert(hdl)
convert_gray_inc_reg(hdl='Verilog')
convert_gray_inc_reg(hdl='VHDL')
Verilog输出代码如下所示:
// File: gray_inc_reg.v
// Generated by MyHDL 1.0dev
// Date: Thu Jun 23 19:06:43 2016
`timescale 1ns/10ps
module gray_inc_reg (
graycnt,
enable,
clock,
reset
);
output [7:0] graycnt;
reg [7:0] graycnt;
input enable;
input clock;
input reset;
wire [7:0] graycnt_comb;
reg [7:0] gray_inc_1_bincnt;
always @(posedge clock, negedge reset) begin: GRAY_INC_REG_GRAY_INC_1_INC_1_SEQ
if (reset == 0) begin
gray_inc_1_bincnt <= 0;
end
else begin
if (enable) begin
gray_inc_1_bincnt <= (gray_inc_1_bincnt + 1);
end
end
end
assign graycnt_comb = ((gray_inc_1_bincnt >>> 1) ^ gray_inc_1_bincnt);
always @(posedge clock, negedge reset) begin: GRAY_INC_REG_REG_0
if (reset == 0) begin
graycnt <= 0;
end
else begin
graycnt <= graycnt_comb;
end
end
endmodule
VHDL输出代码如下所示:
-- File: gray_inc_reg.vhd
-- Generated by MyHDL 1.0dev
-- Date: Thu Jun 23 19:06:43 2016
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use std.textio.all;
use work.pck_myhdl_10.all;
entity gray_inc_reg is
port (
graycnt: out unsigned(7 downto 0);
enable: in std_logic;
clock: in std_logic;
reset: in std_logic
);
end entity gray_inc_reg;
architecture MyHDL of gray_inc_reg is
signal graycnt_comb: unsigned(7 downto 0);
signal gray_inc_1_bincnt: unsigned(7 downto 0);
begin
GRAY_INC_REG_GRAY_INC_1_INC_1_SEQ: process (clock, reset) is
begin
if (reset = '0') then
gray_inc_1_bincnt <= to_unsigned(0, 8);
elsif rising_edge(clock) then
if bool(enable) then
gray_inc_1_bincnt <= (gray_inc_1_bincnt + 1);
end if;
end if;
end process GRAY_INC_REG_GRAY_INC_1_INC_1_SEQ;
graycnt_comb <= (shift_right(gray_inc_1_bincnt, 1) xor gray_inc_1_bincnt);
GRAY_INC_REG_REG_0: process (clock, reset) is
begin
if (reset = '0') then
graycnt <= to_unsigned(0, 8);
elsif rising_edge(clock) then
graycnt <= graycnt_comb;
end if;
end process GRAY_INC_REG_REG_0;
end architecture MyHDL;
请注意,输出是一个平面的“网表列表”,并根据需要生成分层的信号名称。
有限状态机的优化
与通常在硬件设计中一样,有限状态机值得特别注意。
在Verilog和VHDL中,有限状态机通常使用case语句来描述。
Python没有case语句,但是转换器识别特定的if-Then-Else结构并将它们映射到case语句。
当将类型为枚举类型的变量与if-Then-Else结构中的枚举项进行顺序测试时,就会发生此优化。
此外,在Verilog代码中生成了用于适当的高效综合注释。
作为进一步的优化,函数enum得到了增强,使用了额外的参数编码,以优雅地支持不同的编码方案。
例如:
t_State = enum('SEARCH', 'CONFIRM', 'SYNC', encoding='one_hot')
默认的编码是‘二进制’;其他可能的编码是‘one_hot’和‘one_cold’。此参数仅影响转换输出,而不影响类型的行为。
针对case语句生成的Verilog代码进行了优化,以便根据编码进行有效的实现。
请注意,相比之下,Verilog设计器必须进行重要的代码更改才能实现不同的编码方案。
例如,考虑以下有限状态机,其状态变量使用上面定义的枚举类型:
ACTIVE_LOW = bool(0)
FRAME_SIZE = 8
t_State = enum('SEARCH', 'CONFIRM', 'SYNC', encoding="one_hot")
def FramerCtrl(SOF, state, syncFlag, clk, reset_n):
""" Framing control FSM.
SOF -- start-of-frame output bit
state -- FramerState output
syncFlag -- sync pattern found indication input
clk -- clock input
reset_n -- active low reset
"""
index = Signal(intbv(0)[8:]) # position in frame
@always(clk.posedge, reset_n.negedge)
def FSM():
if reset_n == ACTIVE_LOW:
SOF.next = 0
index.next = 0
state.next = t_State.SEARCH
else:
index.next = (index + 1) % FRAME_SIZE
SOF.next = 0
if state == t_State.SEARCH:
index.next = 1
if syncFlag:
state.next = t_State.CONFIRM
elif state == t_State.CONFIRM:
if index == 0:
if syncFlag:
state.next = t_State.SYNC
else:
state.next = t_State.SEARCH
elif state == t_State.SYNC:
if index == 0:
if not syncFlag:
state.next = t_State.SEARCH
SOF.next = (index == FRAME_SIZE-1)
else:
raise ValueError("Undefined state")
return FSM
转换与以前一样完成:
SOF = Signal(bool(0))
syncFlag = Signal(bool(0))
clk = Signal(bool(0))
reset_n = Signal(bool(1))
state = Signal(t_State.SEARCH)
toVerilog(FramerCtrl, SOF, state, syncFlag, clk, reset_n)
toVHDL(FramerCtrl, SOF, state, syncFlag, clk, reset_n)
Verilog输出如下所示:
module FramerCtrl (
SOF,
state,
syncFlag,
clk,
reset_n
);
output SOF;
reg SOF;
output [2:0] state;
reg [2:0] state;
input syncFlag;
input clk;
input reset_n;
reg [7:0] index;
always @(posedge clk, negedge reset_n) begin: FRAMERCTRL_FSM
if ((reset_n == 0)) begin
SOF <= 0;
index <= 0;
state <= 3'b001;
end
else begin
index <= ((index + 1) % 8);
SOF <= 0;
// synthesis parallel_case full_case
casez (state)
3'b??1: begin
index <= 1;
if (syncFlag) begin
state <= 3'b010;
end
end
3'b?1?: begin
if ((index == 0)) begin
if (syncFlag) begin
state <= 3'b100;
end
else begin
state <= 3'b001;
end
end
end
3'b1??: begin
if ((index == 0)) begin
if ((!syncFlag)) begin
state <= 3'b001;
end
end
SOF <= (index == (8 - 1));
end
default: begin
$finish;
end
endcase
end
end
endmodule
VHDL输出如下所示:
package pck_FramerCtrl is
type t_enum_t_State_1 is (
SEARCH,
CONFIRM,
SYNC
);
attribute enum_encoding of t_enum_t_State_1: type is "001 010 100";
end package pck_FramerCtrl;
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use std.textio.all;
use work.pck_myhdl_06.all;
use work.pck_FramerCtrl.all;
entity FramerCtrl is
port (
SOF: out std_logic;
state: inout t_enum_t_State_1;
syncFlag: in std_logic;
clk: in std_logic;
reset_n: in std_logic
);
end entity FramerCtrl;
architecture MyHDL of FramerCtrl is
signal index: unsigned(7 downto 0);
begin
FRAMERCTRL_FSM: process (clk, reset_n) is
begin
if (reset_n = '0') then
SOF <= '0';
index <= "00000000";
state <= SEARCH;
elsif rising_edge(clk) then
index <= ((index + 1) mod 8);
SOF <= '0';
case state is
when SEARCH =>
index <= "00000001";
if to_boolean(syncFlag) then
state <= CONFIRM;
end if;
when CONFIRM =>
if (index = 0) then
if to_boolean(syncFlag) then
state <= SYNC;
else
state <= SEARCH;
end if;
end if;
when SYNC =>
if (index = 0) then
if (not to_boolean(syncFlag)) then
state <= SEARCH;
end if;
end if;
SOF <= to_std_logic(signed(resize(index, 9)) = (8 - 1));
when others =>
assert False report "End of Simulation" severity Failure;
end case;
end if;
end process FRAMERCTRL_FSM;
end architecture MyHDL;
RAM推断
某些综合工具可以推断RAM结构。
为了支持这一特性,转换器将MyHDL中的信号列表映射到Verilog存储器和VHDL数组。
下面的MyHDL示例是一个RAM模型,它使用一个信号列表来对内部内存进行建模。
def RAM(dout, din, addr, we, clk, depth=128):
""" Ram model """
mem = [Signal(intbv(0)[8:]) for i in range(depth)]
@always(clk.posedge)
def write():
if we:
mem[addr].next = din
@always_comb
def read():
dout.next = mem[addr]
return write, read
使用接口端口的适当信号定义,它将转换为以下Verilog代码。
注意信号mem的列表是如何映射到Verilog内存的。
module ram (
dout,
din,
addr,
we,
clk
);
output [7:0] dout;
wire [7:0] dout;
input [7:0] din;
input [6:0] addr;
input we;
input clk;
reg [7:0] mem [0:128-1];
always @(posedge clk) begin: RAM_1_WRITE
if (we) begin
mem[addr] <= din;
end
end
assign dout = mem[addr];
endmodule
在VHDL中,MyHDL信号列表被建模为VHDL阵列信号:
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.pck_myhdl_06.all;
entity ram is
port (
dout: out unsigned(7 downto 0);
din: in unsigned(7 downto 0);
addr: in unsigned(6 downto 0);
we: in std_logic;
clk: in std_logic
);
end entity ram;
architecture MyHDL of ram is
type t_array_mem is array(0 to 128-1) of unsigned(7 downto 0);
signal mem: t_array_mem;
begin
RAM_WRITE: process (clk) is
begin
if rising_edge(clk) then
if to_boolean(we) then
mem(to_integer(addr)) <= din;
end if;
end if;
end process RAM_WRITE;
dout <= mem(to_integer(addr));
end architecture MyHDL;
ROM推断
一些综合工具可以从CASE语句推断ROM内存。
Verilog转换器可以根据更高级别的描述自动扩展为Case语句。
ROM访问是通过索引成整数的元组,在一行中进行描述的。
元组可以手工描述,也可以通过编程方式进行描述。
请注意,使用元组而不是列表来强调内存的只读字符。
下面的示例演示此功能。
ROM访问描述如下:
def rom(dout, addr, CONTENT):
@always_comb
def read():
dout.next = CONTENT[int(addr)]
return read
ROM内容被描述为整数的元组
定义ROM内容后,可以执行以下转换:
CONTENT = (17, 134, 52, 9)
dout = Signal(intbv(0)[8:])
addr = Signal(intbv(0)[4:])
toVerilog(rom, dout, addr, CONTENT)
toVHDL(rom, dout, addr, CONTENT)
Verilog输出代码如下所示:
module rom (
dout,
addr
);
output [7:0] dout;
reg [7:0] dout;
input [3:0] addr;
always @(addr) begin: ROM_READ
// synthesis parallel_case full_case
case (addr)
0: dout <= 17;
1: dout <= 134;
2: dout <= 52;
default: dout <= 9;
endcase
end
endmodule
VHDL输出代码如下所示:
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use std.textio.all;
use work.pck_myhdl_06.all;
entity rom is
port (
dout: out unsigned(7 downto 0);
addr: in unsigned(3 downto 0)
);
end entity rom;
architecture MyHDL of rom is
begin
ROM_READ: process (addr) is
begin
case to_integer(addr) is
when 0 => dout <= "00010001";
when 1 => dout <= "10000110";
when 2 => dout <= "00110100";
when others => dout <= "00001001";
end case;
end process ROM_READ;
end architecture MyHDL;
用户定义代码
MyHDL提供了一种在转换过程中包含用户定义代码的方法,它使用了特殊的函数属性VHDLCODE和VERILOG_CODE。
例如:
def inc_comb(nextCount, count, n):
@always(count)
def logic():
# do nothing here
pass
nextCount.driven = "wire"
return logic
inc_comb.verilog_code =\
"""
assign $nextCount = ($count + 1) % $n;
"""
inc_comb.vhdl_code =\
"""
$nextCount <= ($count + 1) mod $n;
"""
转换后的代码在Verilog中如下所示:
module inc_comb (
nextCount,
count
);
output [7:0] nextCount;
wire [7:0] nextCount;
input [7:0] count;
assign nextCount = (count + 1) % 256;
endmodule
转换后的代码在VHDL中如下所示:
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
use work.pck_myhdl_06.all;
entity inc_comb is
port (
nextCount: out unsigned(7 downto 0);
count: in unsigned(7 downto 0)
);
end entity inc_comb;
architecture MyHDL of inc_comb is
begin
nextCount <= (count + 1) mod 256;
end architecture MyHDL;
在此示例中,将绕过Inc._COME函数的转换,而插入用户定义的代码。
用户定义的代码是一个Python模板字符串,它可以通过基于$的替换来引用MyHDL上下文中的信号和参数。
在转换过程中,将替换适当的层次名称和参数值。
MyHDL代码包含以下任务:
nextCount.driven = "wire"
这指定该模块将作为Verilog wire驱动NextCount信号。